Delivery & retries

At-least-once delivery, deduplication, the retry ladder, and the dead-letter queue.

At-least-once, so dedupe

Delivery is at-least-once. A delivery that stalls mid-flight is reclaimed after 15 minutes and re-sent, so you may occasionally receive the same event twice — dedupe on Drazill-Webhook-Id (idempotent by construction). Record the delivery id the first time you durably accept an event, and ignore a repeat.

A re-sent delivery is signed fresh, so its Drazill-Webhook-Timestamp and Drazill-Webhook-Signature differ from the first attempt while Drazill-Webhook-Id stays the same. Dedupe on the id, never on the signature.

No ordering guarantee

Deliveries are claimed globally by when they are next due, not per endpoint and not per entity, so events can arrive out of order — an order.updated may reach you before the order.created it followed, especially after a retry has pushed one event down the ladder while newer ones flow normally.

Do not reconstruct state from the event stream’s order. Every event carries the ids you need to re-read authoritative state over REST; when order matters, that read is the answer.

Retries and the dead-letter queue

Respond 2xx once you have durably accepted the event. Any non-2xx response or a timeout is a failed attempt. Failed attempts back off on this schedule (seconds), then move to the dead-letter queue:

0 → 30 → 120 → 600 → 3600 → 21600 → 86400 → DEAD_LETTER

That is seven attempts over roughly a day. A payload that fails its own schema is never sent — it fails closed into this same retry/DLQ path rather than delivering unvalidated data.

Inspect and replay

Inspect deliveries and replay one from the API:

curl -sS https://api.drazill.com/api/v1/webhooks/$ID/deliveries -H "X-API-Key: $KEY"
curl -sS -X POST https://api.drazill.com/api/v1/webhooks/deliveries/$DELIVERY_ID/replay -H "X-API-Key: $KEY"

A replay is a new delivery of the same event: it gets a new Drazill-Webhook-Id and re-enters the ladder at attempt 1, while the envelope’s id (the event id) is unchanged. Replays carry replayed_from_delivery_id pointing at the original, and are excluded from your endpoint’s health rates.

Recovering a whole outage

After a receiver has been down long enough to dead-letter a batch, replay the batch in one call instead of one call per row:

curl -sS -X POST \
"https://api.drazill.com/api/v1/webhooks/$ID/replay-dead-letter?since=2026-07-27T00:00:00Z&limit=500" \
-H "X-API-Key: $KEY"
# → {"requeued": 500, "remaining": 128, "since": "…", "limit": 500}

since is required — there is no unbounded form of this call — and is clamped to the dead-letter retention window below. limit caps at 500 per call; remaining tells you whether to call again. Already-replayed deliveries are skipped, so calling twice does not duplicate anything.

Order of operations if the endpoint is suspended: reactivate first, then replay. Bulk replay returns 409 on a suspended endpoint (asking you to reactivate) and on a disabled one (whose queue was already cancelled).

How long deliveries are kept

Delivery history is not retained forever — partly for storage, partly because every attempt row stores up to 4 KB of your receiver’s raw response body.

ClassKept for
Settled deliveries (SUCCEEDED, CANCELLED) and their attempts30 days
DEAD_LETTER deliveries90 days
Deliveries still in flight (PENDING, DELIVERING, FAILED)never pruned

The dead-letter window is the replay window: a dead-lettered delivery can only be replayed while it still exists, which is why it is kept three times as long as a settled one.

Test deliveries bypass your subscriptions

POST /webhooks/{id}/test and POST /webhooks/sandbox/events deliver to the endpoint you name whether or not it subscribes to that event type. That is deliberate — you are testing reachability and your verification code, not your subscription filter — but it means a test delivery is not proof that the same event would reach you in production. Check subscribed_events for that.

Both return 409 WEBHOOKS_DISABLED if webhook delivery is turned off platform-wide, rather than silently succeeding with nothing queued.

Test your integration

  • POST /webhooks/{id}/test — sends a real, signed webhook.test delivery to one endpoint.
  • POST /webhooks/sandbox/events — enqueues any event type with a sample or your own data (validated), for exercising your handler without live activity.

Both go through the exact signing + delivery path production uses, so verifying one proves your verification code. Test and live deliveries are segregated by livemode, so a sandbox event never reaches a live endpoint.

Return 2xx fast and do the real work asynchronously. If you can’t finish within the delivery timeout, accept the event (dedupe on Drazill-Webhook-Id) and process it out of band, rather than holding the connection open.