Webhook retries are repeat attempts a provider makes to deliver the same webhook after an earlier attempt failed. If your endpoint returns a non-2xx status, takes too long or can't be reached, the provider waits and sends the event again, usually with growing gaps (exponential backoff), until it gets a 2xx or its retry window runs out.
attempt 1 09:00:00 → 503 Service Unavailable
attempt 2 09:00:05 → timeout after 15s
attempt 3 09:05:05 → 502 Bad Gateway
attempt 4 09:35:05 → 200 OK ✓ delivered
What triggers a retry
| Your endpoint's response | What most providers do |
|---|---|
2xx | Success, no retry |
3xx redirect | Usually treated as a failure. Many senders don't follow redirects for POST. |
4xx | Usually retried, because the provider can't tell a bug from a transient problem |
429 or 503 | Retried, often honouring Retry-After |
410 Gone | Some providers stop and disable the endpoint |
500, 502, 504 | Retried |
| Timeout or connection error | Retried |
The key point: only 2xx means "delivered". If you return 200 and then crash, the provider considers the event delivered and won't retry. If you return 500 after you already processed it, you'll get it again.
Retry policies vary a lot
- Stripe retries with exponential backoff for up to about three days in live mode.
- GitHub doesn't retry automatically. You redeliver failed deliveries by hand from the webhook settings or the API.
- The Webhook Relay Outbound Gateway retries after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours, for up to 8 attempts within 48 hours, honours
Retry-Afteron429and503(up to one hour), and stops and disables the endpoint on410. - Many smaller providers try a few times over a few minutes, or not at all.
You can't change a provider's policy. If an outage outlasts it, the events are gone unless something in between stored them.
How to handle retries on the receiving side
- Acknowledge fast. Store or queue the event and return
200within a few seconds. A handler that does all its work inline is the most common cause of retries for events that actually succeeded. - Be idempotent. Retries mean duplicates. Record each event ID and skip ones you've seen. See idempotency.
- Return the right status. Return
2xxonly once the event is safely stored. Return5xxif you couldn't store it, so it's retried. Acknowledge event types you don't handle with200so they aren't retried forever. - Don't depend on order. A retried event can arrive after a newer one. Compare timestamps or fetch current state from the provider.
- Watch for failures. A burst of retries usually means a deploy, a bad release or a full disk. Alert on them.
How to retry well when you're the sender
- Use exponential backoff with jitter, so a recovering endpoint isn't flattened by every pending retry at once.
- Keep the same event ID on every attempt so receivers can deduplicate.
- Set a timeout per attempt (5 to 30 seconds is common).
- Cap the total window, then move the event to a dead-letter queue or failed state where it can be replayed.
- Honour
Retry-Afterand back off on429.
Adding a retry layer you control
Putting a webhook gateway between the provider and your endpoint means the provider's retry policy stops being the limit. Webhook Relay's durable retries persist each webhook before the first delivery attempt, run fast retries for a 15-minute handoff window by default, then keep retrying with exponential backoff on a schedule you choose per destination: about 25 minutes, about 16 hours, or up to 30 days. Waiting deliveries show as stalled in the logs with the time of the next attempt. Setup is in the durable webhooks docs, and the retries and idempotency guide goes deeper on receiver design.
