HookGet Open dashboard

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.

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