Retries and idempotency
What a retry preserves, and how a consumer stops processing the same event twice.
In short
- The default schedule is eight attempts over about 21 hours, and it is settable per endpoint.
- A retry keeps the same delivery id, which is what makes it safe to ignore.
- Idempotency has two halves: the producer key and the consumer check.
- 410 Gone stops immediately — that is the consumer unsubscribing, not a failure.
| # | Time | Status | Code | Latency | Why |
|---|---|---|---|---|---|
| 1 | 09:41:02 | failed | 503 | 1,204 ms | Service Unavailable |
| 2 | 09:41:07 | failed | 503 | 980 ms | retried after 5s |
| 3 | 09:41:37 | failed | timeout | 15,000 ms | retried after 30s |
| 4 | 09:46:37 | delivered | 200 | 142 ms | retried after 5m |
Same webhook-id on every attempt, so the consumer can deduplicate.
The schedule
| Attempt | Waits before it |
|---|---|
| 1 | none — immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 30 minutes |
| 6 | 2 hours |
| 7 | 6 hours |
| 8 | 12 hours |
Roughly 21 hours end to end, which crosses a deploy, a certificate renewal and most incidents. A destination that needs something else gets its own schedule:
curl -X PATCH https://api.hookget.com/v1/endpoints/ep_… \
-H "authorization: Bearer $HOOKGET_KEY" \
-d '{"retry_schedule":[0,10,60,300],"max_attempts":4}'
Idempotency, both halves
Producer side. Send an idempotency key with a publish and the same key never produces a second event, however many times the request is retried by your own infrastructure.
-d '{"type":"order.created","payload":{…},"idempotency_key":"ord_10241-created"}'
Consumer side. webhook-id is constant across every retry of a
delivery. Store it and ignore anything you have already handled:
const id = headers['webhook-id'];
if (await seen(id)) return res.status(200).end(); // already handled
await handle(event);
await remember(id); // then remember it
res.status(200).end();
Acknowledge fast and work afterwards. A consumer that does thirty seconds of processing before answering will be retried while it is still working, and then has to be idempotent about its own half-finished work.
What stops the schedule early
| Response | What happens |
|---|---|
| 2xx | Delivered. The attempt is recorded and the failure counter resets |
| 410 Gone | The destination is disabled immediately — it has removed itself |
| 4xx (other) | Retried: a 401 is usually a rotated secret, not a permanent refusal |
| 5xx / timeout / connection error | Retried on the schedule |
| Schedule exhausted | Moved to the dead-letter queue and kept |
Start delivering webhooks today
Point your webhooks at HookGet and watch the first delivery arrive, signed, in under a minute.
Create a free account Try the free webhook tester
10,000 deliveries a month free, no credit card. The free tier blocks rather than bills, so trying it cannot produce an invoice.