Back to sensacat

Home  /  Troubleshooting

· SensaCat Team

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.