Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

Signatures

Every delivery carries a DealMachine-Signature header:
DealMachine-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.
To verify:
  1. Read the raw request body exactly as received. Do not parse and re-serialize it.
  2. Split the header on ,, take t and every v1.
  3. Compute HMAC_SHA256(secret, t + "." + body) and compare it to each v1 with a constant-time comparison.
  4. Reject requests whose t is more than 5 minutes from now.
import { 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); }); }

Secrets

The secret starts with 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.

Test 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.