POST /v1/webhook_endpoints
response. There is no reveal route; a lost secret is replaced by creating a
new endpoint. Subscribe with event_types from the published catalogue; an
unknown type is 400 unknown_event_type rather than a subscription that
silently receives nothing.
Four verification rules
The TypeScript client’sWebhookVerifier enforces all four. If you verify by
hand, so must you.
- Verify the raw bytes. The signed string is
"<t>.<raw body>". A framework that parsed the JSON and re-serialized it has changed the bytes and the MAC will not match. Useexpress.raw,fastifywithrawBody,await request.text()in Workers, or the request stream innode:http. - Compare in constant time. A
===on hex strings leaks the correct MAC one byte at a time. - Bound the timestamp in both directions (default plus or minus 300
seconds).
tis inside the MAC so it cannot be re-stamped, but a captured delivery can be re-sent, and a one-sided check would let a single forged far-future stamp replay forever. - Dedupe on the signed envelope’s
id, never on a header. The signature covers the body and no header, soX-Omega-Deliveryis attacker-controlled on a captured delivery. The verifier returns the signedeventIdfor exactly this;deliveryIdis echoed for logging and is explicitly not authenticated.
What you answer matters
The sender treats every non-2xx as a delivery failure and disables an endpoint after 20 consecutive failures. Re-enabling is an explicitPATCH /v1/webhook_endpoints/{id} with {"enabled": true}; nothing turns an
endpoint back on for you.
Receipt is not processing
By default the verifier records an event as seen the moment it verifies. If your handler then throws, the retry is refused as a replay and that event is gone from your system’s point of view. That default makes the common case, an idempotent handler, effectively-once. For the other trade:manualCommit is that two deliveries of the same event arriving
concurrently can both be processed. Neither default is universally right; pick
the one your handler’s failure mode deserves. The default replay store is
in-memory and correct for one process; several instances need a shared store,
such as Redis or a unique index on the event id.
Delivery semantics
Delivery is at-least-once and unordered. Order on the envelope’screated_at, never on arrival. operator.webhook.* events are audit-only:
they appear in GET /v1/audit and are never delivered, because delivering
them would loop a failing endpoint against itself.