Webhooks & delivery recovery
Verify signed events, deduplicate work and recover failed deliveries without losing messages.
Subscribe to events#
With webhooks:manage, use /webhooks for a generic subscription or /webhook-triggers for filtered events. Both use the same durable delivery system. Destinations must be public HTTPS; private/reserved addresses and redirects are rejected on every attempt.
Events include email.received, email.sent, email.delivered, email.failed, email.bounced, email.complained and email.unknown. Management tests produce the separate webhook.test event.
{
"version": "1",
"id": "stable-event-uuid",
"type": "email.received",
"createdAt": "2026-10-04T10:00:00Z",
"data": {"messageUuid": "mailbox-uuid", "identityUuid": "identity-uuid"}
}
The identity UUID is included when the identity still exists. Fetch current message details using the public UUID rather than assuming the event contains the complete message.
Verify before processing#
The header is X-Webhook-Signature: t=<unix-seconds>,v1=<hex>. Compute HMAC-SHA256 with the subscription secret over timestamp + "." + exactRawBodyBytes. Reject timestamps more than five minutes from your server clock and compare signatures in constant time.
Capture the raw body before parsing JSON. Re-serializing an object changes the signed bytes. Enforce a body-size limit and validate the event version, ID and type after verification.
Store processed event IDs durably. A successful retry or replay can deliver the same event again; only mark it complete after the application work succeeds. Secrets are shown once when created or rotated, not in listing responses. Update the receiver when rotating.
Delivery attempts and replay#
There are eight total attempts, with retry delays of 5 seconds, 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour and 3 hours. After that, a delivery becomes a dead letter.
Inspect /webhook-deliveries, delivery detail and replay operations. Filter by status and traverse page/pageSize; this collection returns deliveries, page, pageSize and total. Test delivery reports the receiver's HTTP outcome: a 200 from the management API is not itself proof that your receiver accepted the event.
Replay preserves the event ID and body and starts a new attempt cycle. Successful/cancelled history becomes eligible for cleanup once the event is 30 days old, provided no pending/retrying delivery or protected dead letter remains. An old replay does not restart that event-age clock. Dead letters remain 90 days after their last update. Pending and retrying deliveries remain durable.
Ownership and disabling#
Subscriptions are user-owned. Revoking the API key that created a webhook does not delete that webhook; disable or delete it separately. Disabled owners cannot receive pending deliveries. Combine notifications with the mailbox change feed to recover from prolonged receiver downtime.