← Blog
·

How to Debug Webhooks: Step-by-Step Troubleshooting

How to debug webhooks: a step-by-step guide

Webhooks fail for a few predictable reasons: the provider never sent the event, the network blocked it, your server returned the wrong status code, or your app processed the request incorrectly. Debug them from the outside in: confirm delivery, inspect the request, verify the signature, check the response, then trace the processing path.

This guide covers how to debug webhooks step by step, why a webhook may not be received, what a timeout means, why retries create duplicates, how to verify HMAC signatures, how to test locally, how to replay events, and how to tell whether the issue is on the provider side or your server.

What a webhook is and how delivery works

A webhook is an HTTP POST sent by a provider when an event happens. Unlike a normal API request, you do not initiate the call yourself. Providers such as Stripe, GitHub, Shopify, and Twilio queue the event, attempt delivery, and retry if the endpoint does not respond as expected.

A typical delivery includes:

  • a delivery ID for a specific attempt
  • an event ID for the underlying event
  • headers such as signature, content-type, and user agent
  • a JSON payload or another provider-specific body format
  • a response expectation, usually a fast 2xx status code

If the endpoint returns a 4xx status code or 5xx status code, or if the request times out, the provider may retry using exponential backoff. That is why the same event can arrive more than once.

Step-by-step: how to debug webhooks

1) Confirm whether the provider attempted delivery

Start in the provider dashboard. Look for the delivery attempt, timestamp, response code, and error message. If the provider shows no attempt at all, the issue may be with event configuration, the endpoint URL, DNS, or the provider itself.

If the provider shows a send attempt but your server logs show nothing, the request likely never reached your app. Check:

  • DNS resolution
  • TLS/SSL certificate validity
  • firewall rules
  • reverse proxy routing
  • endpoint URL typos

2) Inspect the raw request, not just parsed data

Capture the raw request body before any middleware changes it. This matters because HMAC signatures are calculated over the exact bytes sent by the provider. If Express or Next.js parses the body first, signature verification can fail even when the payload looks correct.

Check the following:

  • raw request body
  • headers
  • content-type
  • signature header format
  • delivery ID and event ID

If the payload is malformed, the content-type is wrong, or the schema changed, your parser or validation layer may reject it before business logic runs.

3) Verify the signature correctly

To verify a webhook signature, use the provider’s signing secret and the exact raw request body. Do not verify against a parsed object or reserialized JSON.

Common reasons signatures fail:

  • middleware consumed the body before verification
  • the wrong secret is configured
  • the secret was rotated and the app still uses the old value
  • the request body was modified by whitespace, encoding, or parsing

This is especially common in Express and Next.js. In Express, body-parsing middleware can consume the stream before verification. In Next.js, route handlers and serverless functions may require special raw-body handling depending on the runtime.

4) Check the response code and timing

Most providers expect your endpoint to return a fast 2xx status code once the event is accepted. If your handler does heavy work before responding, the provider may hit a timeout and retry.

A webhook timeout usually points to slow database calls, long-running business logic, downstream API delays, or cold starts in serverless functions. The fix is usually to acknowledge the webhook quickly, then move the real work to background jobs or queues.

5) Determine whether the problem is on the provider side or your server

Use this decision tree:

  • No delivery attempt in the provider dashboard: likely provider configuration, event setup, or endpoint registration issue.
  • Delivery attempt exists, but your server logs are empty: likely DNS, TLS/SSL, firewall, routing, or network reachability.
  • Request reaches your app but fails: likely signature verification, payload validation, middleware, application logic, or response handling.
  • Request succeeds but side effects are wrong: likely duplicate processing, out-of-order events, or missing idempotency.

Provider dashboards, application logs, tracing, and monitoring together give the clearest answer.

Why webhooks are not received

If a webhook is not being received, the most common causes are:

  • the endpoint URL is wrong
  • the server is not publicly reachable
  • DNS is misconfigured
  • TLS/SSL is invalid or expired
  • the provider cannot reach localhost
  • the request is blocked by a firewall or proxy
  • the provider is sending to the wrong environment

For local development, use ngrok or Cloudflare Tunnel so the provider can reach your machine. These tools also help you inspect headers, payloads, and retries in real time.

Why webhook signatures fail in Express or Next.js

Signature failures in Express and Next.js usually come from body parsing.

In Express, middleware such as express.json() can consume the body before signature verification. In Next.js, the request body may be parsed automatically depending on the route type and runtime. To avoid this:

  • verify the signature against the raw request body
  • disable or bypass body parsing where needed
  • confirm the provider’s signing algorithm and secret format

If the provider signs the exact bytes of the request, even a small change in formatting can break verification.

Why webhooks get delivered more than once

Duplicate deliveries are usually normal. Providers retry when they do not receive a timely 2xx status code, when a timeout occurs, or when the connection drops before acknowledgment.

To prevent duplicate webhook processing:

  • store the provider’s event ID or delivery ID
  • use idempotency in your handler
  • apply deduplication before creating side effects
  • make database writes safe to repeat

If you process payments, orders, or notifications, treat every webhook as potentially repeated.

How to handle out-of-order webhook events

Out-of-order delivery happens when events are retried, queued, or processed asynchronously. For example, an update event may arrive before a create event if the provider retries one delivery and your worker processes another first.

To handle this safely:

  • use the event’s timestamp or version field when available
  • store the latest known state in your database
  • ignore stale updates
  • design handlers to be order-tolerant

If the provider offers event sequencing or replay events, use those tools to reconstruct the timeline.

How to test webhooks locally

Local testing is easiest when you can receive real provider traffic on a public URL. Use ngrok for quick setup or Cloudflare Tunnel for a more stable development path.

Good local testing practices:

  • expose a temporary public endpoint
  • send a real test event from the provider dashboard
  • inspect the raw request body and headers
  • confirm the signature matches
  • verify the response code and latency

You can also use webhook inspection tools and provider dashboards to inspect request attempts, replay events, and compare payloads. For framework-specific setup, see the Express guide and the Next.js guide.

For a broader testing workflow, see webhook endpoint testing and what is a webhook.

How to replay a webhook event

Replay events when you need to reproduce a failure after fixing code or configuration. Most providers let you resend a past delivery from the dashboard.

Use replay events to:

  • confirm a signature fix
  • test a new parser or middleware change
  • verify idempotency and deduplication
  • reproduce a timeout or retry scenario

If the provider does not support replay, save the original payload and headers in a secure test environment so you can resend them manually.

What to log when debugging webhooks

Log enough data to correlate provider activity with your server behavior, but avoid logging secrets or full signatures.

Useful fields to log:

  • delivery ID
  • event ID
  • provider name
  • timestamp
  • request path
  • signature verification result
  • response code
  • processing duration
  • retry count, if available
  • trace ID

These logs help you connect delivery attempts, tracing, monitoring, and alerting across the full request path.

Provider-specific notes: Stripe, GitHub, Shopify, and Twilio

Different providers expose different debugging tools.

  • Stripe: delivery attempts, response codes, retries, and replay controls are available in the webhook dashboard.
  • GitHub: recent deliveries, redelivery, and signature details are available in repository or app webhook settings.
  • Shopify: webhook delivery history and endpoint configuration are available in the app admin.
  • Twilio: callback logs and product-specific webhook settings help you inspect request behavior.

Check the provider dashboard for the signing secret, endpoint URL, retry history, and any delivery errors before changing code.

Reliability best practices

A reliable webhook handler should:

  • return a fast 2xx status code after accepting the event
  • validate the payload before processing
  • use idempotency and deduplication
  • move slow work to background jobs or queues
  • monitor failures, latency, and retry spikes
  • alert on repeated signature failures or timeout patterns
  • support secret rotation without downtime

If your system depends on webhooks for billing, fulfillment, or notifications, these controls are not optional.

Debugging checklist

Use this checklist when you need to debug webhooks quickly:

  1. Confirm the provider attempted delivery.
  2. Compare the provider dashboard with your application logs.
  3. Inspect the raw request body and headers.
  4. Verify the HMAC signature with the correct secret.
  5. Check the response code and response time.
  6. Look for retries, duplicates, and out-of-order events.
  7. Test locally with ngrok or Cloudflare Tunnel.
  8. Replay the event after each fix.
  9. Confirm the issue is not caused by middleware, serverless functions, queues, or background jobs.

Conclusion

If the provider dashboard shows no attempt, the issue is probably on the provider side or in event configuration. If the provider shows a send but your server never sees it, check DNS, TLS/SSL, routing, and firewall rules. If the request reaches your app but fails, focus on signature verification, payload validation, middleware, and response behavior.

The safest pattern is simple: accept quickly with a 2xx status code, verify the signature from the raw request body, process work asynchronously, and make every handler idempotent. With the right logs, tracing, monitoring, alerting, and replay workflow, webhook debugging becomes a repeatable process instead of guesswork.

For more help, see webhook debugging and webhook endpoint testing.

Get started with ReqPour

Catch, inspect, and relay webhooks to localhost. Free to start, $3/mo for Pro.