Skip to main content

Webhook retry best practices

A webhook delivery fails whenever the receiver doesn't return a 2xx response, whether from a network glitch, a timeout, or the receiving server being down. Retrying recovers those deliveries, but naive retries create their own problems: duplicate processing, thundering-herd load on a server that just recovered, and events applied out of order. The practices below handle retries without introducing those failures.

Building webhooks?
Svix is the enterprise ready webhooks sending service. With Svix, you can build a secure, reliable, and scalable webhook platform in minutes. Looking to send webhooks? Give it a try!

Retry with exponential backoff

The first attempt fails, so you wait and try again. The key is that the wait grows exponentially rather than staying fixed: a short delay first, then progressively longer ones, up to a cap so you're not waiting days. Growing the interval gives a struggling server room to recover instead of being hammered on a tight loop while it's already overwhelmed. See exponential backoff for the algorithm.

Add jitter to avoid a thundering herd

Exponential backoff alone has a failure mode: if many deliveries fail at once (say, a receiver had a brief outage), they all back off on the same schedule and then retry at the same instant, which can knock the recovering server over again. Jitter adds a small random offset to each retry interval so the retries spread out over time instead of arriving in a synchronized wave.

Publish a concrete retry schedule

The single most useful thing a sender can document is its exact schedule. Providers that say nothing, or only "we retry a few times," leave receivers unable to reason about delivery. As a concrete example, Svix retries a failed delivery on this schedule:

AttemptDelay after previous attempt
1Immediately
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
810 hours

That's 8 attempts spread across roughly 24 hours. Only a 2xx response counts as success; every other status, including 3xx redirects, is treated as a failure and retried. Documenting which status codes trigger a retry is as important as documenting the timing, so receivers know that returning a redirect or a 202 they meant as "accepted" behaves the way they expect.

Move exhausted deliveries to a dead letter queue

Retries can't run forever. After the schedule is exhausted, the event should go to a dead letter queue rather than being dropped, so it can be inspected and replayed later. At Svix, an exhausted message is marked failed and emits a message.attempt.exhausted operational webhook so the sender's system can react. If an endpoint keeps failing over several days it's automatically disabled, which stops wasting attempts on a receiver that's clearly gone.

Make retries safe on the receiving side

Retries mean a receiver will sometimes get the same event more than once, which is normal and expected. Endpoints should therefore be idempotent: deduplicate on the event ID so a redelivered message is recognized and not processed twice. The most common cause of unnecessary retries in the first place is doing slow work before responding, which trips webhook timeouts; acknowledge quickly and process asynchronously.

Let receivers customize the policy

A sensible default schedule won't fit everyone. Where practical, let customers tune their own retry policy, such as the number of attempts or manually replaying failed messages from the point an outage began. This turns a fixed mechanism into one teams can adapt to their own reliability needs.

Frequently asked questions

How many times should a webhook be retried?

There is no universal number, but a common approach is around 8 attempts spread over roughly 24 hours using exponential backoff, after which the message goes to a dead letter queue. What matters most is publishing the exact schedule so receivers can reason about it.

Which HTTP status codes should trigger a webhook retry?

Only 2xx responses should count as success. Every other status, including 3xx redirects and 4xx/5xx errors, should be treated as a failed delivery and retried. Receivers that mean "accepted" should return a 2xx, not a redirect.

Why do webhook retries need jitter?

Without jitter, many deliveries that failed at the same time back off on the same schedule and retry at the same instant, creating a thundering herd that can take down a server that just recovered. Jitter adds a small random offset so retries spread out over time.

How do retries cause duplicate webhooks?

A receiver can process an event, then fail or time out before its 2xx response reaches the sender. The sender sees no success and retries, so the receiver gets the event again. Deduplicating on the event ID makes reprocessing harmless.