Skip to main content
Webhook deliveries carry three headers:
Endpoint secrets are returned once, in the 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’s WebhookVerifier enforces all four. If you verify by hand, so must you.
  1. 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. Use express.raw, fastify with rawBody, await request.text() in Workers, or the request stream in node:http.
  2. Compare in constant time. A === on hex strings leaks the correct MAC one byte at a time.
  3. Bound the timestamp in both directions (default plus or minus 300 seconds). t is 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.
  4. Dedupe on the signed envelope’s id, never on a header. The signature covers the body and no header, so X-Omega-Delivery is attacker-controlled on a captured delivery. The verifier returns the signed eventId for exactly this; deliveryId is 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 explicit PATCH /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:
The cost of 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’s created_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.