Currently Available: Need a skilled Software Developer for your next project?
Categories
APIs

Webhook Delivery: Retries, Timeouts, and Debugging

When your service receives a webhook, another service has sent it an HTTP request to report an event, such as a payment update or a code change. Your endpoint needs to accept that request quickly and safely. If it responds too slowly or returns an error, the sender may retry, stop delivering, or require you to recover the missed event yourself.

A reliable receiver verifies each request, then stores the event so it survives a crash before returning a successful HTTP response. It handles slower work in the background and accounts for the same event arriving more than once or events arriving out of order. The exact timeout and retry rules depend on the sender, so check each provider’s policy rather than assuming one standard applies.

Each sender sets its own deadline and retry policy

A sender generally treats a timely HTTP 2xx response, with a status code from 200 to 299, as successful receipt. A timeout or other response may count as a failed delivery. The deadline and what happens after failure vary by provider:

Provider Documented delivery behavior What to plan for
Stripe In live mode, Stripe retries for up to three days with exponential backoff, meaning the delay between attempts increases. Its sandbox behavior is different: three retries over a few hours. Stripe also supports manual resends through the Dashboard for up to 15 days and through the CLI for up to 30 days after event creation. Handle repeated deliveries and distinguish live from sandbox behavior. A manual resend does not necessarily stop automatic retries.
GitHub GitHub records a delivery as failed if it takes longer than 10 seconds and does not automatically redeliver failed deliveries. Return a 2xx response within the deadline and arrange your own review and recovery of failures.
Shopify Shopify treats a delivery as failed if the endpoint does not respond within five seconds. The time between retries increases, and Shopify can remove subscriptions after multiple failures. Watch delivery attempts and response times so repeated failures do not go unnoticed.
Svix Svix describes a 2xx response within 30 seconds as successful for its service. Use the limit for Svix integrations only. It is not a general webhook timeout.

These limits come from each provider’s documentation: Stripe’s webhook guidance, GitHub’s failed-delivery guidance, Shopify’s troubleshooting guidance, and Svix’s receiving guide. A service that receives webhooks from several providers should meet the shortest relevant deadline while following each provider’s retry and recovery rules.

Record the event before acknowledging it

Return a successful response as soon as your service has verified the request and safely recorded the event. A durable record is data that survives a process crash, such as a committed database row or a message accepted by a durable queue. Your handler should not return success just because it started processing: if the service stops before recording the event, the sender may treat the delivery as complete while your service loses the event.

In a common design, the service checks the sender’s signature to confirm the request came from that sender and has not changed. It then stores the event or adds it to a queue for later work before returning 2xx. A background worker performs slower tasks, such as calling another API or updating several records. Both Stripe and GitHub recommend moving complex work out of the request path so the endpoint can respond promptly (Stripe, GitHub).

Stripe requires the original, unmodified request body for its signature check. If a request fails verification, check that your code uses the correct secret, signature header, and body representation before changing retry settings.

Make repeated and late deliveries safe

A timeout does not prove that your handler did nothing. Your service might finish the work while the response gets lost, leaving the sender to retry. The receiver should therefore assume that the same event can arrive more than once. Stripe documents duplicate deliveries and recommends tracking event IDs; it also warns that events can arrive out of order.

Store a stable provider event or delivery ID and use it to prevent the same event from causing irreversible work twice. For example, a payment handler should not issue a second refund just because the sender redelivered an event. Record the event and create its work item in the same database transaction when possible, so a crash cannot leave the event marked as accepted without corresponding work.

Distinct events can also describe the same underlying change, so an event ID alone does not resolve every duplicate-effect problem. Apply domain rules to the action itself, such as checking whether a refund has already been issued. For late or reordered events, check that each event moves the object from a valid current state to a valid next state, or retrieve the provider’s current object before changing your stored state. Do not use event timestamps as a substitute for deduplication or guaranteed ordering.

Trace a failed delivery from sender to worker

Start with the provider’s delivery record, then match it against your infrastructure and application logs. A delivery record shows what the sender attempted and what response it observed; it does not show every step inside your network or confirm that background work completed. GitHub also notes that delivery logs can take a few minutes to show an event.

  1. Find the attempt. Confirm that the sender triggered the expected event and used the active subscription and correct endpoint URL. Note the attempt time, response code, duration, and reported failure reason.
  2. Classify the failure. A DNS error, which means the hostname did not resolve to a network address, or a connection error points toward routing, firewall rules, or reachability. A TLS error, involving the protocol that secures the connection, points toward the certificate or certificate chain. For a timeout, compare the sender-reported duration with the proxy and load-balancer logs (which record how requests pass through your network) and the handler timing. An HTTP error calls for inspection of the status code and response body.
  3. Follow the request through your service. Match the provider’s event ID against request logs, signature-check results, database or queue writes, response status, and background-job outcome. If the request reached your service but no job ran, investigate how the service passed the work to storage or the worker rather than the sender’s network path.
  4. Reproduce the failure. Test duplicate deliveries, slow handling, non-2xx responses, and a lost response after the event has been recorded. A webhook debugger such as Svix Play can simulate custom response codes and random failures.

Keep secrets out of logs, and avoid storing more sensitive payload data than debugging requires. Log enough information to distinguish a request that never reached your endpoint from one that your endpoint accepted but a worker failed to process.

Recover events after retries stop

Automatic retries have limits, and some senders do not provide them at all. Track accepted events that remain unprocessed and failed deliveries that need review. Give operators a safe way to replay work while preventing the same event from causing the same action twice, as you do for ordinary deliveries.

When an outage creates a gap, use the provider’s manual resend tools or API where available. For events that cannot be replayed, compare your stored state with the provider’s current records and correct any differences. GitHub recommends finding and redelivering failed deliveries because it does not retry automatically; Stripe provides bounded manual resend windows, which do not replace duplicate-safe processing.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

Your email address will not be published. Required fields are marked *