Retries and failures
The schedule
A delivery that does not answer 2xx is retried on a fixed schedule. Delays
are measured from the attempt that just failed:
| After attempt | Next attempt in |
|---|---|
| 1st | immediately |
| 2nd | 1 minute |
| 3rd | 5 minutes |
| 4th | 30 minutes |
| 5th | 2 hours |
| 6th | 6 hours |
| 7th | 12 hours |
| 8th | 24 hours |
| 9th | — dead-lettered |
Nine attempts over roughly 45 hours. Each delay gets ±20% jitter, so a burst of failures does not come back at your endpoint in lockstep — treat the column as approximate.
After the last one the delivery is marked dead and never retried
automatically. You can still replay it by hand.
What counts as a failure
Anything that is not 2xx. Explicitly:
| Response | |
|---|---|
2xx | Success. Resets the endpoint’s failure counter. |
410 Gone | Dead immediately, and the endpoint is disabled. See below. |
3xx | A failure. We never follow redirects — see below. |
4xx (other) | A failure. Retried. |
5xx | A failure. Retried. |
| Timeout, DNS failure, TLS error, connection refused | A failure. Retried. |
A 401 or 404 during your own deploy is far more often transient than
permanent, so we retry them rather than giving up. That is a deliberate
choice: it means a five-minute deploy window costs you nothing.
410 Gone is the one that stops everything
410 is the single status that means “stop, permanently”. It dead-letters
that delivery on the spot — no remaining retries — and flips the endpoint to
auto_disabled, so no further events are delivered to it at all until you
re-enable it.
Return 410 only when you mean it. Some frameworks return 410 for a
soft-deleted route; if that is yours, make sure it cannot fire on your webhook
path. The alternative — us hammering a decommissioned URL for months — is why
we honour it.
Everything else, including 404, retries.
Redirects are never followed
A 3xx is treated as an ordinary failed delivery. We do not follow the
Location header, because doing so would let a redirect jump past the SSRF
checks that ran against the original URL.
If your endpoint has moved, PATCH the endpoint’s url. A redirect will not
work.
The timeout
We give up on a single attempt after 10 seconds, measured as a total deadline, not just idle time — trickling bytes will not hold the connection open past it.
Acknowledge fast and process afterwards. A handler that does its real work inline and takes 11 seconds looks identical to an outage from our side, and you get a retry (and a duplicate) for work you already completed.
Delivery is at-least-once
You will receive the same event more than once. Not as a rare edge case — as a normal consequence of retrying: your handler can succeed and time out on the response, and the retry that follows is a second copy of work you already did.
Deduplicate on the body’s id:
// The event id is stable across every retry AND across a manual replay.
if (await alreadyProcessed(event.id)) return res.status(200).send();
await recordProcessed(event.id);DriveCars-Delivery-Id is not the deduplication key — it changes per
attempt. That is what makes it useful in logs and useless here.
Order is not guaranteed either. A booking.confirmed that needed three
retries can land after a booking.cancelled that succeeded first time. If
ordering matters to your logic, compare against the state in data rather
than assuming arrival order.
When an endpoint is disabled
An endpoint’s status goes to auto_disabled on any of:
410 Gone— immediately, whatever the failure count.- 50 consecutive failures.
- 72 hours with no successful delivery. Measured from the last success, or from registration if there has never been one.
The second and third are independent — whichever comes first. The 72-hour rule matters because the later backoff steps are long: a low-volume endpoint can be broken for three days without ever accumulating 50 attempts.
A disabled endpoint receives nothing. Events for your partner account are
still recorded and readable via GET /v1/events, so
nothing is lost — but nothing is pushed either.
We email the contact address on your partner account when this happens.
Re-enabling
curl -sS -X PATCH "https://{your-base-url}/v1/webhooks/42" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'Both auto-disable clocks reset in the same write: the consecutive-failure counter goes to zero, and the 72-hour no-success clock restarts from the moment you re-enable. Re-enabling is you telling us the endpoint works again, so neither rule holds its old grievance against it.
Fix the endpoint first, though. A re-enabled endpoint that is still broken simply starts accumulating failures again from zero, and disables itself once more — later than before, but no less certainly.
POST /v1/webhooks/{id}/test is the
cheapest way to confirm it really is fixed — provided the endpoint is
subscribed to ping, which is a good reason to include it in
event_types on every endpoint you register. An endpoint not subscribed to
ping receives nothing from that route, and you will have to wait for a real
event instead.
Fix the endpoint before re-enabling. Events that dead-lettered while it was
down are not redelivered automatically — replay them, or read
the gap from GET /v1/events.
Inspecting deliveries
curl -sS "https://{your-base-url}/v1/webhooks/42/deliveries?limit=25" \
-H "Authorization: Bearer $TOKEN"Newest first, cursor-paginated like every other list — follow next_cursor.
{
"id": 5591,
"event_id": "evt_9c1f4a7b2e8d05364f1a9b7c3d2e5f80",
"endpoint_id": 42,
"status": "dead",
"attempt": 9,
"next_attempt_at": "2026-09-09T12:00:00.000Z",
"replay_of": null,
"attempts": [
{ "at": "2026-09-07T10:00:00.000Z", "outcome": "failed", "statusCode": 502, "bodyExcerpt": "upstream timeout" }
],
"created_at": "2026-09-07T10:00:00.000Z",
"updated_at": "2026-09-09T09:00:00.000Z"
}attempts is the per-attempt log — the status code we got and up to 1 KB of
your response body. That excerpt is usually the fastest way to find out what
your own endpoint said when it failed, especially behind a proxy that swallowed
the error.
The log is capped at the 20 most recent attempts. With a nine-attempt ceiling per delivery you will not hit that on a single delivery; a heavily replayed one can.
status is one of pending, in_flight, succeeded or dead.
Replaying
curl -sS -X POST "https://{your-base-url}/v1/deliveries/5591/replay" \
-H "Authorization: Bearer $TOKEN"Note the path: /v1/deliveries/{id}/replay, keyed on the delivery id, not
the endpoint id.
This queues a new delivery of the same event to the same endpoint, with a
fresh attempt count and its own full retry schedule. The response is the new
delivery row, with replay_of pointing at the original.
The event id in the body is unchanged — so a replay of something you already
processed hits your deduplication and does nothing. That is the intended
behaviour, and it is what makes replaying a whole range safe.
The endpoint must be active. Replaying to a disabled endpoint dead-letters without sending. Re-enable first.