Webhook Delivery Failures Explained
What each response tells the sender, how long they keep trying, and how to get back the events you already lost.
A webhook delivery fails when the sender does not get a 2xx within its timeout. What happens next depends entirely on the provider, and the differences are larger than most people expect.
What your response tells the sender
| Your response | Sender's interpretation | Retried? |
|---|---|---|
| 200-299 | Delivered | No |
| 3xx | Usually treated as a failure | Yes, and some senders cap redirects |
| 400-499 | Your fault, permanent | Usually yes, then given up on |
| 500-599 | Your fault, temporary | Yes |
| Timeout | Unknown; may have been processed | Yes, so duplicates are likely |
| TLS error | Cannot connect | Yes, until the window closes |
| Connection refused | Nothing listening | Yes |
The timeout row is the one that produces duplicates. If your handler processed the event and then took too long to answer, the sender records a failure and sends the same event again. Your handler must be idempotent or you will charge someone twice.
How long providers actually keep trying
Check your own provider's current documentation rather than trusting a blog post, including this one. These windows change, and stale figures circulate for years.
| Provider | Retry window | After it closes |
|---|---|---|
| Stripe (live mode) | Up to 3 days, exponential backoff | Endpoint disabled, owner emailed |
| Shopify | About 8 attempts over 4 hours | Subscription removed entirely |
| GitHub | Configurable per app; deliveries kept 30 days | Redeliver manually from the UI |
| PayPal | Retries over several days | Event marked undelivered |
Shopify is the outlier and the dangerous one. Four hours does not survive an overnight deploy problem, and the consequence is not a lost event but a removed subscription, after which no events of that topic are sent at all.
A great deal of writing still cites 19 retries over 48 hours for Shopify. Their current troubleshooting documentation says 8 attempts over about 4 hours.
Diagnosing where it breaks
Work outward from your own server. Each of these narrows the problem considerably.
That third check catches a real and confusing case. A missing intermediate certificate works in desktop browsers, which cache intermediates, and fails for server-to-server callers that do not. Your site looks fine and every webhook fails.
Also confirm the sender is not being blocked. WAF rules, Cloudflare bot protection and rate limiting reject webhook traffic routinely, and the rejection never reaches your application logs.
Recovering events after the window closes
Redelivery from the provider's UI works for a handful of events. For an outage spanning hours, query the provider's API for the affected period and replay against your handler.
Where the provider has no event-listing API, reconcile against object state instead: fetch every order or charge modified during the window and compare against your database. That is slower and it is the only option that works when the events are genuinely gone.
Making failures visible
A delivery failure that repeats is noticeable. A delivery that succeeded while your worker did nothing is not, and neither is a stream that simply stopped.
Covering both needs a check on the processing side rather than the delivery side, which is what how to know if a webhook is being delivered works through.