Skip to main content

Guide

How to Set Up Webhook Integrations

Receiving, verifying, and processing webhooks reliably -- signature validation, idempotency, retry handling, and async processing patterns for production systems.

Category Guide
Read Time 8 min read
Updated April 2026
Steps 5 steps

Who This Guide Is For

This guide is for developers integrating with third-party services that send webhooks — Stripe, GitHub, Twilio, Shopify, payment processors, CRMs, or any service that pushes event data to your application via HTTP callbacks. You understand HTTP and have a web application that can receive POST requests. You want to handle webhooks in a way that is secure, reliable, and does not lose events when things go wrong.

Before You Start

You should have a web application with an accessible HTTP endpoint (the webhook receiver) and credentials or configuration access to the service that will send the webhooks. You should also have a queue system in place (or be prepared to implement one), because processing webhooks synchronously is a reliability problem this guide will address.

Review the sending service’s webhook documentation before building anything. Understand: what events are available, what the payload structure looks like, how they sign payloads for verification, what their retry policy is, and what response codes they expect. These details vary significantly between providers and directly shape your implementation.

Step 1: Set Up the Receiving Endpoint

The webhook endpoint is a URL in your application that receives POST requests from the external service. It should be purpose-built for webhook processing — not a general-purpose controller repurposed for webhooks.

Use a dedicated route and controller for each webhook source. If you receive webhooks from Stripe and from a CRM, use separate endpoints for each. The payload formats, signature verification methods, and processing logic are different, and mixing them in a single endpoint creates unnecessary complexity.

Exempt webhook routes from CSRF protection. Webhooks are POST requests from external services, not from your application’s forms. CSRF middleware will reject them because they do not carry a CSRF token. In Laravel, add the webhook routes to the CSRF middleware’s exception list. In other frameworks, apply the equivalent exemption.

Return the correct status code immediately. The external service expects a response within a few seconds. Return 200 (or 202 Accepted) as quickly as possible to acknowledge receipt. Do not process the webhook payload inline — if your processing takes longer than the service’s timeout (typically 5 to 30 seconds), the service will assume delivery failed and retry, creating duplicate events.

Log the raw payload before processing. If something goes wrong during processing, you need the original payload to debug and replay the event. Store the raw request body (before JSON decoding or any transformation) along with the headers, timestamp, and source IP. This log is invaluable for diagnosing integration issues.

Step 2: Verify Webhook Signatures

Webhook signature verification is the mechanism that confirms a request genuinely came from the expected service and has not been tampered with. Skipping verification means anyone who discovers your webhook URL can send fake events that your application will process as real.

Most services sign webhooks using HMAC-SHA256. The service computes a hash of the request body using a shared secret, and includes the hash in a request header. Your endpoint computes the same hash using your copy of the shared secret and compares the two. If they match, the request is authentic.

The signing secret is separate from your API key. Services typically provide a webhook signing secret that is distinct from the API key you use for outbound requests. Store this secret securely and use it exclusively for signature verification.

Use constant-time comparison when comparing the computed signature against the one in the header. Standard string comparison functions can be vulnerable to timing attacks — an attacker can determine the correct signature one character at a time by measuring response times. Use hash_equals in PHP, hmac.compare_digest in Python, or the equivalent constant-time comparison in your language.

Verify before processing. The signature check should be the first thing your endpoint does after reading the request body. If verification fails, return a 401 or 403 status code and log the attempt. Do not process the payload, do not queue it, do not parse it. Reject it immediately.

Handle signature verification edge cases. Some services include a timestamp in the signed payload and reject replayed events that are too old. Stripe, for example, includes a timestamp and recommends rejecting events with timestamps more than five minutes old. Implement this check if the service supports it — it prevents replay attacks where a captured webhook is re-sent later.

Step 3: Process Webhooks Asynchronously

The single most important reliability pattern for webhook processing is: acknowledge receipt immediately, process the payload later. This decouples the speed of your response from the complexity of your processing logic.

Queue the processing. When the webhook endpoint receives a valid, verified payload, store it (in a queue job, a database row, or a message queue) and return 200 immediately. A separate process — a queue worker, a background job, a cron task — picks up the stored payload and processes it. This means your endpoint responds in milliseconds regardless of how long the processing takes.

Why this matters for reliability: external services have timeout thresholds and retry policies. If your endpoint takes 20 seconds to process a webhook and the service’s timeout is 10 seconds, the service will retry the delivery. Now you are processing the same event twice concurrently. If your processing is not idempotent (Step 4), you have a data integrity problem.

Design your queue jobs to handle failures. If processing fails (a database connection drops, a dependent service is unavailable), the job should be retried automatically after a delay. Most queue systems support configurable retry logic with exponential backoff. Set a maximum retry count so that persistently failing events do not clog the queue.

Monitor your queue depth. If webhook processing falls behind — events are arriving faster than the queue can process them — you need to know about it before the queue grows so large that processing latency becomes unacceptable. Set up monitoring that alerts when the queue depth exceeds a threshold or when the oldest unprocessed event is more than a few minutes old.

Step 4: Implement Idempotent Processing

Idempotency means that processing the same event multiple times produces the same result as processing it once. This is essential for webhook integrations because duplicate deliveries are not a bug — they are a feature of reliable webhook systems.

Why duplicates happen: the sending service retries when it does not receive a timely response (your server was slow, there was a network blip, the connection was reset). Your endpoint might have processed the event successfully but returned the 200 too late. Now the same event arrives again. If your handler creates a new record on every delivery, you have duplicates.

Use the event ID for deduplication. Most webhook providers include a unique event ID in the payload. Store processed event IDs in a table and check against it before processing. If the event ID already exists, skip processing and return 200. The lookup should be fast — index the event ID column.

Design handlers to be naturally idempotent where possible. Instead of “add 10 to the balance,” design the handler as “set the balance to the value in the event.” Instead of “create a subscription record,” design it as “create or update the subscription record based on the external ID.” Upserts (insert or update) are naturally idempotent because repeating the operation produces the same result.

Be cautious with side effects. If your webhook handler sends an email, triggers another API call, or creates a record in a third-party system, those side effects will occur on every duplicate delivery unless you guard against them. Check whether the action has already been performed before executing side effects.

Step 5: Handle Event Ordering and Timing

Webhooks do not arrive in order, and they do not arrive instantly. Your processing logic must account for events that arrive out of sequence and for gaps between related events.

Out-of-order delivery is common when a service processes events in parallel. You might receive a subscription.updated event before the subscription.created event, or a payment.succeeded event after a payment.refunded event for the same payment. Your handlers must not assume that events arrive in the order they occurred.

Use event timestamps (most services include them) to determine the actual sequence of events. When processing an update, check whether a more recent update has already been processed. If your stored data reflects a later timestamp than the incoming event, discard the incoming event — it is stale.

Handle missing predecessor events. If you receive a subscription.updated event for a subscription that does not exist in your system (because the subscription.created event has not arrived yet or was lost), your handler has two options: create the record from the update event’s data (if the payload contains enough information), or queue the event for reprocessing after a delay (hoping the predecessor arrives in the meantime).

Reconciliation is the safety net for event-based integrations. Periodically (daily or weekly), compare your local data against the source system’s data via their API. This catches events that were lost, processed incorrectly, or never sent. It is not a replacement for reliable webhook processing, but it ensures that small discrepancies do not accumulate into large data integrity problems.

Common Mistakes

  • No signature verification. Without it, anyone who discovers your webhook URL can send fake events. This is a security vulnerability, not a corner case.
  • Synchronous processing. Processing the webhook inline blocks your response, causes timeouts, triggers retries, and creates duplicate events. Queue the work and respond immediately.
  • No idempotency. Duplicate deliveries are guaranteed over time. If your handler is not idempotent, you will have duplicate records, double-charged customers, or duplicate notifications.
  • Assuming event ordering. Events can arrive in any order. A handler that assumes created arrives before updated will break in production.
  • No monitoring. If your webhook queue backs up or your handler starts failing, you need to know immediately. Silent webhook processing failures are invisible until a customer reports that their data is wrong.

What Good Looks Like

A well-implemented webhook integration has: dedicated endpoints per webhook source with CSRF exemption, signature verification on every request using constant-time comparison, asynchronous processing via a queue with retry logic, idempotent handlers that produce correct results regardless of duplicate deliveries, handling for out-of-order events, raw payload logging for debugging, queue monitoring with alerts, and periodic reconciliation against the source system.

Next Steps

For the Stripe-specific webhook patterns, How to Integrate Stripe Payments covers the payment webhook lifecycle in depth. For the API design patterns your webhook endpoints sit alongside, How to Structure a REST API covers REST conventions. For the broader integration planning process, see How to Plan an API Integration.

Need Hands-On Help?

Our guides give you the thinking. If you want someone to do the building, we should talk.

Start a Project Browse Case Studies