Article · Updated November 1, 2026 · By the Hooks team

Stripe webhook retries and idempotency

Short answer: If your endpoint does not answer with a 2xx quickly, Stripe retries the delivery with exponential backoff for up to three days. Because any delivery can arrive more than once, store event.id and make the handler idempotent. Acknowledge first, then do the slow work asynchronously.

Retries are a feature: they are why a deploy or a short outage does not lose payments. They also mean every handler has to tolerate repeats.

What counts as a failure

Any non-2xx response, a timeout, or a connection error triggers a retry. Slow handlers fail as surely as broken ones, so acknowledge immediately and enqueue the real work.

Deduplicate on event.id

The same event can be delivered more than once even without a failure, for example when the response is lost after your handler committed. Insert event.id into a unique table before acting, and skip the work when the insert conflicts.

Idempotent handler

-- processed_events(event_id TEXT PRIMARY KEY)
INSERT INTO processed_events (event_id) VALUES ($1)
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

-- No row returned means this event was already handled.

Keep a delivery log

When a handler fails after the retry window closes, you need the event back. A gateway that stores every delivery, with its response status and the provider's id, lets you replay it instead of asking Stripe to resend.

Frequently asked questions

How long does Stripe keep retrying?

Stripe retries failed deliveries with exponential backoff for up to three days. After that the event is only available through the dashboard or API, which is why replaying from your own delivery log is faster.

Is checking the event type enough to avoid double-processing?

No. Two different events can describe the same state change, and the same event can arrive twice. Deduplicate on the event id, not on the type.

Keep reading