How delivery works
A webhook is not a fire-and-forget HTTP call. Every event is written to a queue first, delivered from there, and retried on a schedule that spans three days.
The path an event takes
The queue is the important part. The event is durable before anyone tries to deliver it, so an event is never lost because a delivery attempt happened at an unlucky moment.
The retry schedule
Thirteen attempts, spanning roughly 72 hours:
| Attempt | When |
|---|---|
| 1 | Immediately |
| 2 | +1 minute |
| 3 | +5 minutes |
| 4 | +30 minutes |
| 5 | +2 hours |
| 6β8 | +4h, +8h, +12h |
| 9β13 | every 12 hours out to +72h |
Tight early because most failures are transient β a deploy, a restart, a blip. Sparse later because an endpoint that has been down for a day is not coming back in the next minute, and hammering it costs us both.
Each delay carries Β±10% jitter. When an endpoint recovers, everything queued behind it would otherwise fire in the same instant and knock it straight back over.
Your endpoint can be down for a full working day and still receive everything. The window is sized for that on purpose: a deploy, an incident or an overnight outage should cost you latency, never events.
What "at least once" asks of you
We guarantee delivery at least once, not exactly once. Exactly-once delivery is not something anyone can offer over a network; the honest engineering is to deliver repeatedly and let you deduplicate.
So your handler must be idempotent. Deduplicate on the event id, or drive fulfilment off a state transition your own database owns:
Code
What your endpoint should do
Answer 2xx fast. Any 2xx is success; anything else is a retry. Verify the signature, write the event down, return. Do the slow work afterwards.
Do not fulfil inline. If shipping an order takes eight seconds, you will time out, we will retry, and you will ship twice. Acknowledge, then process from your own queue.
Do not trust the body without checking the signature. The URL is public. See verifying signatures.
Return 2xx for events you do not care about. A 404 on an event type you have not implemented looks like an outage and burns all 13 attempts.
Things that surprise people
Order is not guaranteed. A retried payment.success can arrive after a later event. Your state machine should reject backwards transitions rather than assume arrival order.
A 3xx is not success. We do not follow redirects on delivery. Give us the final URL.
Slow is failure. An endpoint that takes 30 seconds to answer is treated as down, because from our side it is indistinguishable from down.
Manual resend exists. If you lost events during an outage that outlasted the window, resend from the dashboard rather than reconciling by hand.
Next steps
- Webhooks overview β the event catalogue and signature verification
- Payment states β making fulfilment exactly-once
- How a payment works β why webhooks exist at all

