Webhooks & Events

Handling Webhook Retries

Providers retry deliveries that appear to fail. Understanding when and why lets you cooperate instead of fighting it.

What you'll learn

  • Explain what triggers a webhook retry
  • Describe typical backoff and retry-window behaviour
  • Make processing safe for repeated deliveries
  • Decide when to fail loudly versus accept and defer

6 min read

What counts as a failure

To a provider, a delivery has failed if it did not receive a timely 2xx response. That single rule covers several cases: a non-2xx status code, a connection that was refused outright, or a timeout because your handler took too long to answer. In each of these, the provider assumes you never got the event and schedules another attempt to make sure you do.

Crucially, a retry can happen even when you did successfully process the event — for instance if your reply was lost on the way back, or arrived just after the timeout expired. So retries are not only about your code crashing; they are about the provider's fundamental uncertainty over whether you received the message. Your receiver must be built with that uncertainty firmly in mind from day one.

Backoff and retry windows

Providers almost always retry with exponential backoff: a short wait before the first retry, then progressively longer gaps between subsequent ones. This gives a struggling endpoint room to recover instead of being hammered with rapid-fire repeats that would only make matters worse. Retries continue for a bounded window — typically hours or sometimes days — after which the delivery is abandoned for good.

Because that window is finite, a long enough outage can mean permanently missed events that no retry will ever bring back. This is precisely why a reconciliation strategy matters: when your endpoint comes back online, you may need to fetch what you missed yourself rather than wait for retries that have already stopped firing. That recovery path is covered in replaying missed events.

Make repeats harmless

Since the same event may legitimately arrive several times, your processing must be idempotent — running it twice must leave exactly the same result as running it once. The standard way is to record which event ids you have already handled and skip any duplicates on sight. Without this guard, retries can double-charge a customer, double-send an email, or otherwise quietly corrupt your state in ways that are painful to unpick.

if (alreadyProcessed(event.id)) return respond(200);
process(event);
markProcessed(event.id);
return respond(200);

Returning 200 for a duplicate stops the provider from retrying it any further while keeping your data correct. That simple acknowledgement is what makes retries safe rather than dangerous.

Fail loudly or accept and defer

When something genuinely goes wrong on your side, you face a choice. You can return a non-2xx and lean on the provider's retries as a free, built-in backoff mechanism — which is ideal for transient issues such as a brief database outage that will clear on its own shortly. Alternatively, you can accept the event with a 2xx, store it durably, and retry the processing internally on your own schedule for much finer control.

Both approaches are entirely valid, and mixing them depending on the situation is common and sensible. Whichever you choose, log every attempt together with its event id so you can reconstruct what happened. The provider's exact retry policy, the spacing of attempts, and the request timeout are all documented in the API documentation.

Key takeaways

  • A retry fires whenever the provider does not see a timely 2xx
  • Retries use exponential backoff over a bounded, finite window
  • Idempotent processing keeps duplicate deliveries harmless
  • Choose between provider retries and your own internal retry queue

FAQ

Will I get the same webhook more than once?

Yes, you should expect it. A lost or late response makes the provider retry even after you processed the event, so design every handler to tolerate duplicates.

How long does a provider keep retrying?

For a finite window, often escalating with backoff over hours or days. After it expires the delivery is dropped, which is why a reconciliation poll is a useful backstop.

Should I return an error to trigger a retry?

You can — a non-2xx leans on the provider's backoff for transient failures. Alternatively, accept the event and retry it from your own queue for more control.

Integrate with Merion

Ready to build?

Read the API reference, grab the OpenAPI spec, and ship a resilient integration.