DealMachine-Signature header:1DealMachine-Signature: t=1756224000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e98a4d9d2c9b3f4c5d6e7f8a9b
t is the Unix time the request was signed. v1 is an HMAC-SHA256, hex encoded, of the string "{t}.{raw body}" keyed with your webhook's secret. During a secret rotation the header carries two v1 values (one per secret) for 24 hours; a match on either is valid.,, take t and every v1.HMAC_SHA256(secret, t + "." + body) and compare it to each v1 with a constant-time comparison.t is more than 5 minutes from now.123456789101112131415161718import { createHmac, timingSafeEqual } from "node:crypto"; export function verify(header: string, rawBody: string, secret: string): boolean { const parts = Object.fromEntries( header.split(",").map((part) => part.trim().split("=") as [string, string]) ); const timestamp = Number(parts.t); if (!Number.isFinite(timestamp)) return false; if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false; const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest(); return header .split(",") .filter((part) => part.trim().startsWith("v1=")) .some((part) => { const candidate = Buffer.from(part.trim().slice(3), "hex"); return candidate.length === expected.length && timingSafeEqual(candidate, expected); }); }
whsec_ and is shown once: in the create response, and again in the rotate response. Store it as you would an API key. POST /v1/webhooks/{id}/rotate-secret issues a new one and keeps the old one valid for 24 hours so you can switch without dropping deliveries.POST /v1/webhooks/{id}/test sends a ping event, signed exactly like a real one, and returns the status code and time your endpoint answered with.