HookGet עברית Start free

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.
msg_01J8ZQ3F9VBAQ4E1S0TZY6P8YV order.created · 2 units
#TimeStatus CodeLatencyWhy
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.

Every attempt against one destination: what was sent, what came back, how long it took, and how long we waited before trying again. This is the answer to “did you send it?” without a database query.

The schedule

AttemptWaits before it
1none — immediate
230 seconds
32 minutes
410 minutes
530 minutes
62 hours
76 hours
812 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

ResponseWhat happens
2xxDelivered. The attempt is recorded and the failure counter resets
410 GoneThe 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 errorRetried on the schedule
Schedule exhaustedMoved 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.