Log In

How to make webhook processing reliable

Webhooks let services notify your application about events such as payments or repository changes. Unlike polling, they arrive as HTTP POST requests that your endpoint must handle correctly every time. Failures in verification, slow responses, or missing deduplication lead to lost updates, duplicate actions, or security issues. Reliable webhook processing rests on five practices: signature verification, immediate acknowledgements, retry awareness, idempotency, and tolerance for out-of-order delivery. These steps come from official documentation for major providers and apply to any webhook sender that uses HTTP callbacks.

Verify every incoming request with signatures

Without verification an attacker can send fake events that trigger order fulfillment or account changes. Stripe includes a signature in the Stripe-Signature header computed with HMAC-SHA256 over the exact request body and a timestamp. Your code must read the raw body before any framework parses it, because re-serializing JSON changes whitespace or key order and breaks the match.

Libraries supplied by the provider perform the check and also enforce a default five-minute tolerance on the timestamp to block replay attacks. Use NTP on your server so the clock stays synchronized. IP allow-listing offers a second layer but is insufficient alone because addresses can be spoofed or change.

Core Rule Return a 2xx status code before any complex logic. Verify the signature on the raw request body. Log every event ID before processing it.

Acknowledge delivery before doing work

The sender treats any 2xx response as successful delivery. Anything else, including timeouts or redirects, counts as failure and triggers retries. Stripe therefore requires you to return 200 before updating invoices or other long-running tasks. GitHub similarly terminates connections that exceed ten seconds without a 2xx reply.

When traffic spikes, such as at the start of a billing month, synchronous processing can overwhelm your server. The safe pattern is to place the event on an internal queue and return success immediately. Background workers then perform the actual work with their own error handling and retry logic.

Expect automatic retries and design for them

Stripe retries failed deliveries for up to three days in live mode using exponential backoff. Sandbox deliveries receive only three attempts over a few hours. If your endpoint is disabled when a retry occurs, future attempts stop. Re-enabling the endpoint before the next scheduled retry resumes the schedule.

Manual resends do not cancel the provider’s automatic retry behavior. Therefore your handler must remain idempotent even after you have manually replayed an event. Track the next expected retry time in your monitoring dashboard so you can investigate persistent failures before the three-day window closes.

Detect and ignore duplicate deliveries

At-least-once delivery means the same event can arrive more than once. Stripe states that endpoints may receive the same Event object multiple times and sometimes generates two distinct Event objects for the same underlying change. The recommended deduplication key is the event ID for the first case and the combination of data.object.id plus event.type for the second case.

Store processed event IDs in a durable table with a unique constraint. Attempt the insert before any business logic; if the insert fails because the ID already exists, return 200 without further action. This atomic check prevents race conditions when the same event arrives on two parallel connections.

Do not rely on event creation order

Stripe does not guarantee that events arrive in the order they were generated. A subscription.created event may reach your endpoint after the corresponding invoice.paid event. Snapshot timestamps are recorded only to the second, so two distinct events can share the same created value. Use the event ID to detect duplicates rather than comparing created times.

When an event arrives out of order, retrieve any missing objects through the provider’s API instead of waiting for the earlier event. This approach keeps your local state consistent without depending on delivery sequence.

Combine the practices into a single handler

A reliable webhook handler performs the following steps in order: read the raw body, verify the signature, parse the JSON, attempt to record the event ID atomically, return 200 immediately if the ID already exists, otherwise enqueue the payload for background processing. Each step is small, fast, and independently testable. The same pattern appears in guidance from Stripe and independent analyses of webhook reliability.

Document the exact headers and body format your endpoint expects. Include a short example request in your internal runbook so on-call engineers can reproduce failures quickly. Review the handler whenever the provider updates its retry or signing behavior.

Sources

See our free AI tools →