Retry & idempotency
Allison retries failed deliveries up to six times over ~32 hours. Every retry reuses the same event id so deduping is a one-line check.
What counts as success
A delivery is considered successful when your receiver returns a 2xx status within 10 seconds. Anything else is a failure:
- Any non-2xx status (including 3xx — we don't follow redirects)
- TCP connect failure, TLS handshake failure, DNS failure
- Timeout after 10 seconds
- Connection reset mid-response
Retry schedule
Retries are scheduled at increasing intervals from the initial attempt:
| Attempt | Delay from previous | Approx. elapsed |
|---|---|---|
| 1 | immediate | 0 |
| 2 | + 1 minute | ~1 min |
| 3 | + 15 minutes | ~16 min |
| 4 | + 1 hour | ~1h 16min |
| 5 | + 6 hours | ~7h 16min |
| 6 | + 24 hours | ~31h 16min |
After the sixth failure, delivery is marked permanently failed. You can still see the attempt in the dashboard (Settings → Webhooks → Deliveries) or via GET /v1/webhook-subscriptions/{id}/deliveries.
Idempotency: dedup on event.id
Every event has a stable id (evt_<32 hex>). Retries of the same event reuse the same id. Your receiver should track which event ids it has already processed and acknowledge duplicates immediately with 200.
// Pseudo-code for a minimal idempotent receiver
async function handle(req, res) {
const eventId = req.headers['x-allison-event-id'];
if (await seenEventIds.has(eventId)) {
return res.status(200).end(); // dedupe
}
// Verify signature (see /docs/webhooks/signing) BEFORE recording, so
// a forged request can't poison the dedup set.
if (!verify(req)) return res.status(401).end();
const event = JSON.parse(req.body);
await doWork(event);
await seenEventIds.add(eventId, { ttlDays: 7 });
return res.status(200).end();
}The event id is also in the envelope body (event.id), so you can read it either way. Keeping dedup entries for 7 days covers the full 32h retry window with plenty of margin.
Auto-disable
We automatically disable a subscription when it's clearly broken, so dead endpoints don't accumulate noise. Two triggers:
410 Goneresponse. Immediate disable. The receiver is signalling "this endpoint is retired, stop sending." Use this when you permanently remove a webhook handler.- Rolling failure rate ≥ 90% over 24 hours. Disabled once the subscription has seen at least 10 deliveries in the window and the failure rate crosses the threshold. Prevents a broken endpoint from exhausting retries forever.
Disabled subscriptions stay in the dashboard with an Auto-disabled badge and auto_disabled_reason field. Re-enabling via the dashboard or PUT /v1/webhook-subscriptions/{id} with { "enabled": true } clears the auto-disable state and resumes delivery on the next matching event.
Ordering
Events are delivered in the order they're generated for a given subscription, but we don't guarantee strict ordering under retry pressure. If you need ordered processing, sort on created_at in the envelope — it's assigned when the event is generated, not when it's delivered.
Next steps
- Webhooks overview
- Signing — verify the HMAC header before processing
- Events — payload shape for each event type