All guides

LEARN \u00b7 DEBUGGING GUIDE

Webhook fires twice: how to debug duplicate webhook deliveries

A Stripe webhook or GitHub event hits your endpoint. Your handler processes it. Ten seconds later, the same event arrives again. The provider thought the first delivery failed.

IntermediateCache/queue/distributed bugs

What this usually means

Most webhook providers (Stripe, GitHub, Shopify, etc.) use at-least-once delivery. If your endpoint does not respond with a 2xx status code quickly enough, the provider assumes delivery failed and retries. The retry can arrive while your first handler is still processing. Even if you return 200, network latency can cause the provider to time out waiting for your response. The provider has no way to know your handler started processing — it only knows it did not get a timely acknowledgement.

( 01 )Fast diagnosis

The first ten minutes \u2014 establish facts before touching code.

  • 1Check the webhook provider's dashboard. Look at the delivery log for the event. How many attempts? What response did each get? What was the latency?
  • 2Check if your handler processes events synchronously before returning the HTTP response. If processing takes 10 seconds, most providers will have already retried.
  • 3Verify your handler is idempotent. Does it check the event ID and skip if already processed?
  • 4Check if your endpoint is behind a load balancer or proxy that adds latency or buffers responses.
  • 5Look at your server logs. Two requests with the same event ID within a short window means the provider retried.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchWebhook provider dashboard — delivery attempts, response codes, latency per attempt
  • searchWebhook handler code — is processing synchronous or async? Is there an event ID deduplication check?
  • searchServer access logs — two POST requests with the same payload within seconds
  • searchDatabase — event processing log or idempotency table
  • searchLoad balancer / reverse proxy timeout settings
  • searchProvider documentation — retry policy and timeout values
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningHandler does all processing synchronously before returning 200 — the response takes too long, provider retries
  • warningNo idempotency check on the event ID — every delivery is treated as a new event
  • warningLoad balancer or reverse proxy has a timeout shorter than the handler processing time
  • warningHandler returns 200 but crashes after — the provider sees the 200 and does not retry, but the operation is incomplete
  • warningMultiple app instances both pick up the same webhook event before either marks it processed
  • warningThe provider's retry interval is shorter than the handler's processing time
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildAcknowledge the webhook immediately: return 200 as soon as you validate the signature and persist the raw payload. Process asynchronously.
  • buildStore the event ID in a database with a unique constraint. Check it before processing. Skip if already seen.
  • buildUse a job queue: the webhook handler enqueues a job and returns 200. The job worker processes with retries.
  • buildSet load balancer and proxy timeouts to be generous enough for your handler to respond.
  • buildLog every webhook receipt with event ID and timestamp — this creates an audit trail for debugging duplicates.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedSend a test webhook from the provider's dashboard. Confirm you receive exactly one processed event.
  • verifiedSimulate a slow handler by adding a sleep. Confirm the provider retries but your idempotency check skips the duplicate.
  • verifiedSend the same event ID twice. Confirm the second delivery is ignored.
  • verifiedCheck the database: the event should appear exactly once in the processing log.
  • verifiedRun a load test with rapid webhook deliveries and verify zero duplicate side effects.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningProcessing webhooks synchronously in the HTTP handler for anything beyond trivial operations
  • warningNot logging the raw webhook payload on receipt — makes debugging duplicates nearly impossible
  • warningTrusting the provider will never send a duplicate — they all do
  • warningNot setting up idempotency from day one — retrofitting it after duplicates cause data problems is painful
  • warningIgnoring webhook signature verification in the rush to return 200 quickly