Webhook Retries and Timeouts: How Stripe, GitHub, Shopify and Others Retry

Most webhook senders treat a non-2xx response or a slow reply as a failed delivery and retry it: Slack waits 3 seconds and retries 3 times, Shopify waits 5 seconds and retries 8 times over 4 hours, Stripe retries for up to 3 days and GitHub not at all.

A webhook retry is the sender delivering the same event again because your endpoint answered with a non-2xx status or didn't answer within the sender's timeout, and every provider sets its own timeout, schedule, and give-up rule. The safe design is the same everywhere: reply 2xx within a few seconds, do the work in the background, and skip events you've already processed. The table below compares nine senders, using numbers from each provider's docs.

Webhook retry policies by provider

Every sender defines its own webhook retry policy.

The strictest timeouts are short (Slack gives you 3 seconds, and Shopify gives you 5 seconds in total with just 1 second to connect), so you have to build your receiver for the shortest webhook timeout you integrate with. And give-up rules differ a lot, from GitHub, which never retries on its own, to Shopify, which deletes Admin API subscriptions after 8 consecutive failures.

ProviderResponse timeoutAutomatic retriesWhen it stops or disables
StripeNo number published; reply 2xx before complex logicExponential backoff for up to 3 days (sandbox: 3 times over a few hours)Stops retrying an event if the endpoint is disabled or deleted
GitHub10 secondsNone; redeliver by hand within 3 daysFailed deliveries stay failed until redelivered
Shopify1 second to connect, 5 seconds in total8 retries over 4 hoursDeletes Admin API subscriptions after 8 consecutive failures
Slack Events API3 seconds3 retries: nearly immediately, after 1 minute, after 5 minutesDisables event subscriptions if over 95% of attempts fail in 60 minutes (apps under 1,000 events an hour exempt)
Twilio5 seconds to connect, 15 seconds total by default1 retry after a connection timeout by default; up to 5 with #rc=Set per URL with #rc= and #rp=
PayPalNo number publishedUp to 25 times over 3 daysMarked Failed; resend from the Webhook Events dashboard
Twitch EventSubA few secondsResends when unsure a notification arrivedRevokes the subscription (notification_failures_exceeded)
Zendesk12 secondsTimeouts up to 5 times; 409 up to 3 timesNever deactivated by failures; a circuit breaker can pause sends
Svix (Standard Webhooks)30 seconds8 attempts over about 27 hours 35 minutesDisables the endpoint after 5 days of failures

What counts as a failed delivery

  • Any HTTP status code outside the 2xx range counts as a failure.
  • No answer within the sender's timeout is treated as a failed delivery, even if your code finished the work a second later.
  • Redirects usually fail: Stripe and Svix count 3xx responses as failures, Shopify treats 3XX codes as errors, and Slack follows at most two redirects.

Most senders attach headers so you can spot a retry or tell them to stop. Slack marks retries with x-slack-retry-num and x-slack-retry-reason (like http_timeout), and returning a non-200 response with x-slack-no-retry: 1 tells Slack to drop the event. Twitch includes a Twitch-Eventsub-Message-Retry header and keeps the same Twitch-Eventsub-Message-Id when it resends, while Twilio adds an I-Twilio-Idempotency-Token header. If you receive from a Svix-based sender, returning webhook-delivery: abort-message in your response stops any further attempts.

Design your endpoint for retries

  1. Answer with a 2xx status fast and push the real work to a queue or background job, since Stripe explicitly tells you to return 2xx before running any complex logic that could time out.
  2. Treat every incoming request as a potential duplicate and record its unique ID: Stripe's event id, X-GitHub-Delivery (which stays the same when redelivered), X-Shopify-Webhook-Id, Twitch-Eventsub-Message-Id, or webhook-id for Standard Webhooks.
  3. Return 2xx when you skip a duplicate you've already seen, or the sender will treat your rejection as a failure and keep retrying.
  4. Make your side effects idempotent, as Zendesk advises, so an event that slips through twice doesn't charge a card or send an email twice.

Insert the event ID into your database in the same transaction as the business logic, so a crash halfway through can't mark an event as done when the work never ran.

CREATE TABLE processed_events (
  event_id    text PRIMARY KEY,
  received_at timestamptz NOT NULL DEFAULT now()
);

-- No row back means we already handled this event
INSERT INTO processed_events (event_id)
VALUES ($1)
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Recover events after an outage

If your endpoint goes down, check our webhook not received guide to diagnose the outage itself, then use each sender's recovery tools to pull the missed events back in.

GitHub never retries failed deliveries on its own. You have to redeliver failed attempts from the past 3 days manually in the webhook's settings page or through the GitHub API.

After up to 25 retries over 3 days, PayPal marks the delivery Failed, and you can resend it from the Webhook Events dashboard.

Shopify retries 8 times over 4 hours, then deletes the webhook subscription automatically if it was created through the Admin API. After an outage, check that your subscription still exists and create it again if Shopify removed it.

Stripe retries in live mode for up to 3 days, but if your endpoint was disabled or deleted when a retry fires, Stripe stops future retries for that event. A Svix-based sender disables your endpoint if all attempts fail for 5 days, and Twitch changes your subscription status to notification_failures_exceeded and revokes it if you fail to respond quickly enough too many times.

Test retries and duplicate handling

A free webhooks.sh bin replies 200 to every request it captures, with a fixed JSON body like {"ok":true,"delivery_id":"..."}, so a sender has no reason to retry, and it can't return a 500 or simulate a slow reply on purpose. What it does well is capture a real delivery with its headers and raw body, so you can see the exact ID header you need to dedupe on. Once you capture a payload, send that same request to your own webhook endpoint twice and verify that the second copy returns 200 without running your job again.

Free bins last 48 hours and take up to 100 requests a day, and you can claim a bin with a free Google sign-in to keep the URL and store new requests for 30 days. On the Solo plan ($12 a month on the pricing page), the wsh CLI replays captured requests straight to localhost, and forwarding from your webhooks.host endpoint to your own HTTPS URLs retries failed sends up to 3 attempts within an hour.

for i in 1 2; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    -H "X-GitHub-Delivery: <delivery id>" \
    -H "X-Hub-Signature-256: <signature from the bin>" \
    --json @payload.json http://localhost:3000/webhooks
done

FAQ

How many times does Stripe retry a webhook?

In live mode, a Stripe webhook retry runs for up to 3 days using exponential backoff, while sandbox events are retried 3 times over a few hours. Each retry arrives with a new Stripe-Signature timestamp and signature.

Does GitHub retry failed webhooks?

No, there is no automatic GitHub webhook retry. If your server fails or takes more than 10 seconds to reply, GitHub marks the delivery as a failure, and you have to redeliver it manually within 3 days (which keeps the original X-GitHub-Delivery header).

What is a good webhook timeout?

Design your receiver to reply within the strictest timeout of any sender you use: 3 seconds for Slack, 5 seconds for Shopify, 10 seconds for GitHub, 12 seconds for Zendesk, and 30 seconds for Svix. Put the incoming payload on a queue and return 200 before doing any heavy work.

What happens if my webhook endpoint is down?

It depends on the sender: Shopify retries 8 times over 4 hours and then deletes Admin API subscriptions, Slack temporarily disables event subscriptions if over 95% of attempts fail in 60 minutes (for apps getting 1,000+ events an hour), and PayPal retries 25 times over 3 days.

Why am I receiving the same webhook twice?

Webhook senders aim for at-least-once delivery, so if your endpoint takes too long to answer or drops the connection, the sender retries even if your server already ran the code for the first attempt. Deduplicate incoming requests using the sender's event or delivery ID header.

Do webhook retries arrive in order?

No, retries and normal deliveries aren't guaranteed to arrive in sequence, and Stripe notes that it doesn't guarantee event delivery order at all. When order matters, use the webhook as a trigger to fetch the latest state from the provider's API.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.