1
Create an endpoint
developers.webhook.create returns the signing secret once. Store it before the next call — no operation returns it again, and the way to get a new one is to rotate.developers.webhook.test sends a delivery to the endpoint on demand, which is how you check the handler before an event depends on it.2
Verify the signature
Every delivery carries
Lath-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 over the string <t>.<raw body> using the endpoint’s secret.Use the raw body. Parsing the JSON and re-serialising it produces different bytes — a different key order or a different space is a different signature — and the check will fail for reasons that look like nothing. Read the body as text, verify, then parse.The timestamp is inside the signed string rather than beside it, so it cannot be swapped for a fresh one. Deliveries more than five minutes old are rejected by default.3
Verify it without the SDK
The scheme is deliberately small enough to implement anywhere. Compare in constant time — a byte-by-byte comparison that returns early leaks how much of the signature was right.
4
Verify it on an edge runtime
The version above imports
node:crypto, which Cloudflare Workers only provide with nodejs_compat. This one uses WebCrypto alone, so it runs on Workers, Deno, Bun, Vercel Edge and a browser without a flag.crypto.subtle.verify does the comparison itself and does it in constant time, so there is no hand-written compare to get wrong. The secret is used exactly as it was issued, prefix included.5
Rotate the secret without dropping a delivery
developers.webhook.secret.rotate is there so a leaked secret does not mean a broken endpoint. There is no overlap window: the old secret stops verifying the moment the rotation commits, because deliveries are signed when they are attempted rather than when they are queued.So deploy the new secret promptly. Deliveries that fail in the gap are not lost — they retry on the normal schedule and succeed once your receiver has the new secret. The endpoint keeps its id, its URL, its event filter and everything already queued against it.6
When your endpoint is down
A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours and 24 hours — seven attempts over about a day and a half. After the last one it is marked
dead rather than dropped, and it stays readable and replayable.Every attempt keeps its own row with its status, error and duration, so a failure can be read rather than guessed at. developers.webhook.delivery.get returns one, and developers.webhook.deliveries.list filters by status: queued, delivering, delivered, failed or dead.A delivery is replayable once the endpoint is back — one at a time or in bulk — which is the point of storing the attempt rather than only the outcome.developers.webhook.disable stops deliveries without deleting the endpoint or its history; enable resumes.7
Answer fast
Acknowledge with a 2xx as soon as the signature checks out, and do the work afterwards. An endpoint that finishes its processing before replying is an endpoint that times out under load and then receives the same event again.Treat delivery as at-least-once: the same event can arrive twice. Key your handler on the event id so the second copy is a no-op.

