How to Troubleshoot and Debug Webhooks: A Complete Guide
When a webhook isn't working, the problem is usually one of five things: the event never arrived (missing events), it arrived multiple times (duplicate deliveries), your handler took too long (timeouts), the signature check failed (verification errors), or the processing logic has a bug. This guide provides a systematic approach to diagnosing and fixing each of these failure modes, followed by a hands-on tutorial using Stripe webhooks and the Hookdeck CLI.
For the theoretical background on webhook debugging, see our guide to troubleshooting webhooks.
Systematic debugging flowchart
When a webhook isn't working as expected, follow these steps in order:
- Check the provider's delivery status. Most providers (Stripe, Shopify, GitHub) show webhook delivery attempts in their dashboard. Verify the webhook was actually sent and check the response they received.
- Verify the URL configuration. Confirm the webhook URL in the provider's dashboard matches your endpoint exactly — including protocol (HTTPS), path, and port. A single character difference will route webhooks to the wrong place.
- Inspect the request. Use the Hookdeck dashboard, the CLI, or your server logs to confirm the request was received. Check the full headers, body, and metadata.
- Check signature verification. If the request arrives but your handler rejects it, the most common cause is a signature verification failure. See Fixing signature verification errors below.
- Check your processing logic. If the request is accepted but processing fails, inspect your application logs for errors. Common issues: database constraint violations, null pointer exceptions, external API failures.
- Check retry behavior. If the event was delivered but failed, check whether retries are configured and whether subsequent attempts succeeded. Review all delivery attempts, not just the first one.
- Verify idempotency. If you're seeing duplicate processing, confirm your handlers are idempotent. Check for race conditions in concurrent webhook processing.
Common webhook failure modes
Missing events
Symptoms: Your provider shows webhooks were sent, but they never arrive at your endpoint.
Common causes and fixes:
- URL misconfiguration: Double-check the URL in your provider's webhook settings. Copy-paste rather than typing manually.
- DNS resolution failure: Your domain might not resolve from the provider's infrastructure. Test with
digornslookupfrom an external machine. - Firewall or CDN blocking: Some firewalls or CDNs (Cloudflare, AWS WAF) block requests that don't look like browser traffic. Whitelist your provider's IP ranges if available.
- Server not running: Your application crashed or wasn't deployed. Check your hosting platform's logs.
- Challenge/handshake not completed: Some providers (Facebook, Zoom) require an initial verification handshake before sending webhooks. Hookdeck handles these automatically.
Duplicate deliveries
Symptoms: The same event is processed multiple times, causing duplicate records or duplicate side effects.
Root cause: At-least-once delivery means duplicates are expected — from provider retries, network issues, or infrastructure behavior.
Fix: Implement idempotent processing using a unique event identifier:
async function handleWebhook(req, res) {
const eventId = req.headers['x-hookdeck-eventid'] || req.body.id;
// Check if already processed
const existing = await db.processedEvents.findOne({ eventId });
if (existing) {
return res.status(200).send('Already processed');
}
// Mark as processing (with unique constraint)
await db.processedEvents.insert({ eventId, status: 'processing' });
// Process the event
await processEvent(req.body);
// Mark as complete
await db.processedEvents.update({ eventId }, { status: 'completed' });
res.status(200).send('OK');
}
Timeouts
Symptoms: Your handler starts processing but the provider (or Hookdeck) reports a timeout. The event is then retried, potentially causing duplicate processing.
Root cause: Your handler takes longer to respond than the timeout window (typically 5-30 seconds for most providers, 60 seconds for Hookdeck).
Fix: Acknowledge immediately and process asynchronously:
app.post('/webhook', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously (queue, background job, etc.)
await queue.add('process-webhook', { payload: req.body });
});
With Hookdeck, your application already receives webhooks from a durable queue — you don't need to add another queue. Simply process synchronously within the timeout window, or use rate limiting to control throughput.
Fixing signature verification failures
Symptoms: Webhooks arrive but your handler returns 401/403 because signature verification fails.
Debugging steps:
- Verify you're using the correct secret. Check your provider's dashboard for the current signing secret. Secrets may change when you rotate credentials.
- Verify you're reading the raw body. If you parse the body (e.g., with Express's
json()middleware) before verification, the re-serialized JSON may differ from the original. Always verify against the raw request body. - Check the encoding. Compare whether your provider sends Base64 or hex-encoded signatures, and ensure your hash output matches.
- Check for timestamp expiry. For providers with timestamped signatures (Stripe, Svix), retried webhooks may fall outside the validation window. Consider disabling timestamp checks or increasing the tolerance.
- Use the provider's SDK. When available, the provider's official SDK handles encoding and comparison details correctly.
For more on webhook security, see our Webhook Security Vulnerabilities Guide.
Replay debugging
Event replay — re-delivering events for debugging or recovery — is one of the most powerful troubleshooting tools available:
- Reproduce issues: Replay a specific event to reproduce a bug in your handler without triggering a new event from the provider.
- Test fixes: After fixing a bug, replay the event that triggered it to verify the fix works.
- Recover from outages: After resolving an incident, bulk replay all events that failed during the outage.
With the Hookdeck CLI, press r to replay any event to your local server. In the dashboard, replay events individually or use bulk retry for mass recovery. Replay is safe when your handlers are idempotent.
Hands-on tutorial: Debugging Stripe webhooks locally
Setting up Stripe webhooks locally
In this tutorial, we will go through how to locally receive and debug Stripe webhooks. At the end of this guide, we will have a setup that receives Stripe webhooks locally and gives us visibility into the headers and payload of each webhook request.
Setting up Stripe webhooks
The first step in this exercise is to set up a Stripe account. Stripe is the webhook provider, so we will be subscribing to Stripe events and receiving webhooks on a local server.
At the time I am writing this, all you need to set up a Stripe account is your email address. If you already have a Stripe account, you can proceed to the next section but if not, head over to the Stripe registration page to set up a new account. You will be required to verify your email address after you register.
Cloning a Node.js API project
Next, we need a local server to receive webhooks from Stripe. You can use any local server of your choosing, written in your preferred language, as long as you ensure that it is running on a specific port. For this exercise, we will use a Node.js sample API available on the Hookdeck GitHub repository.
You can clone this repository by running the following command:
git clone <https://github.com/hookdeck/nodejs-webhook-server-example.git>
This will make the project available at the location where you ran the command on your file system.
Navigate to the root of the project and install the required dependencies by running the following commands:
cd nodejs-webhook-server-example
npm install
When the installation completes, you can then run the Node.js server with the following command:
npm start
This will boot up the API application and print a message to the screen indicating that the API is now running and listening for connections on port 1337.
The endpoint to be used for the webhook requests is /stripe-webhooks-endpoint and can be found in the src/routes.ts file as shown below:
router.post(
"/stripe-webhooks-endpoint",
bodyParser.raw({ type: "application/json" }),
function (req, res) {
console.log(req.body);
res.send("Stripe Successfully received Webhook request");
},
);
This endpoint receives the webhook requests, prints the request body to the console, and returns a message.
Receiving webhooks on localhost
In order to debug webhooks locally, we need to be able to receive webhooks in our local environment. This is not possible out of the box as local running servers are not publicly available on the internet. Therefore, we will use the Hookdeck CLI because it is built specifically for debugging webhooks, as opposed to the others which have to be configured, and sometimes coupled with other tools, to work with webhooks.
You can also use our online webhook test tool if you prefer not to install a tool or don’t have the privileges to on the system you’re using to debug.
Set up Hookdeck CLI
Visit Hookdeck's CLI documentation to install and set up the tool on your operating system.
Once the setup process is complete, the next step is to use the CLI to generate a webhook URL that points to the running API application. To do this, run the following command:
hookdeck listen 1337
This command starts an interactive session where the CLI collects information about the endpoint you're about to create. Below are the questions and answers you should supply to each question. Ensure to hit the Enter key after each answer.
| Question | Answer |
|---|---|
| What source should you select? | Create new source |
| What should your new source label be? | stripe |
| What path should the webhooks be forwarded to (i.e.: /webhooks)? | /stripe-webhooks-endpoint |
| What's the connection label (i.e.: My API)? | My Stripe API |
With this information, the CLI will begin the process of generating the URL and once it's done, you will see the URL printed to the screen and the CLI indicating that it is ready to receive requests.

Copy the webhook URL, as it will be required for setting up webhooks on Stripe.
Register to Stripe webhook
Now that we have completed the setup on the destination application, it's time to subscribe for a webhook on Stripe.
On your Stripe dashboard, go to Developers → Webhooks. On the "Webhooks" page, click on the + Add endpoint button at the top right-hand side of the screen. This action will pop up a dialog similar to the one below:

On the dialog, add the webhook URL copied from the CLI into the Endpoint URL field. Next, click the Events to send dropdown and select the account.updated event which will be triggered each time your Stripe account is updated. Click the Add endpoint button to complete this process.
This will create the connection between your Stripe account and the API application running on your local machine for the account.updated event. After the webhook has been successfully added, you will see a screen like the one below:

Test Stripe webhook
With our webhook connection set up, it's now time to test it. Stripe provides a way to send a test webhook that simulates the event you have registered for. This is very handy for testing and debugging purposes.
On the top right-hand corner of your webhook screen (shown above), click on the Send test webhook button. This will pop open a dialog for you to select the event for which you want to send the test. See dialog below:

Make sure you select the account.updated event and click the Send test webhook button. This will trigger a webhook request to your webhook URL, which will be received at the endpoint you specified when creating the URL (i.e. /stripe-webhooks-endpoint).
Observe the terminal window where you ran the hookdeck listen 1337 command. You will see the webhook request printed to the terminal as shown below:

We are now successfully receiving our Stripe webhooks locally.
Debugging Stripe Webhooks
Inspecting webhook headers
The last item in the information printed on the CLI for the received webhook is an endpoint for you to view details about the webhook request just received. Copy this URL and load it in your browser, and you will see an event details screen like the one below.

This screen contains a rich amount of details about the webhook you just received.
When working with webhooks, some of the important information sent in the webhook request is contained in the request headers. This can be authentication information like API tokens, cache information or custom headers that carry signatures to verify the source of the webhook.
If for some reason your webhooks are showing up in the terminal but your API endpoint is not receiving them, you want to check the headers to ensure that your endpoint is being authenticated.
The request headers for the received webhook are displayed in the Headers section of the event screen, as shown below:

As seen in the above request, a Stripe signature is sent in the stripe-signature header. This signature allows you to verify that the webhooks are being sent by Stripe. You can verify Stripe signatures either using official Stripe libraries or by developing your own solution. Details on rolling your own verification system can be found here.
Inspecting webhook payload
Apart from being notified about an event that took place on a source application, another responsibility of webhooks is to transfer data from the source application to the destination application.
This is data you will often need to do some processing on your API endpoint based on the event that occurred. When debugging, you want to ensure that you're getting all the data required and in the right formats. Sometimes you will need to coerce into different data types to achieve the processing required at your end.
For example, when a user pays through your Stripe gateway, you will expect to receive the paid amount in a numeric format. Stripe might decide, for some reason, to send this value in string format. This can mess up the calculations or processing you need to do on your end, but if you inspect the value and discover that it was sent as a string, you would be able to include logic to coerce the value into a numeric format.
The event screen has a Body section where you can inspect all the parameters sent in the body of the request, as shown below:

Retrying webhooks
Debugging is a continuous cycle of test→ inspect → fix → test. When you fire a test webhook from Stripe and discover an issue or anomaly in your system, you inspect the request and your code to track the bug, fix it, then test again.
Oftentimes, when dealing with a bug, you may need to run tests multiple times. You don't want to be going back to your Stripe account to simulate a test over and over, as this can quickly become frustrating.
Don't worry, the Hookdeck CLI has you covered. There are two ways to retry an event.
- Event Page: At the top right-hand corner there is a retry button, as shown below.

- Dashboard CLI View: The retry button is on the edge of the event.

Click this button to retry the webhook request after making a fix, then inspect once again to see if the bug has cleared. This handy tool removes the stress of manually retrying the webhook request over and over.
Attempt Response
When an event is retried, Hookdeck will generate a new attempt. It is useful to look at request response and server status.

Bookmark a webhook
Having to manually trigger the event in Stripe or send a test notification can become cumbersome. You can __bookmark (save)__ a request that you can retry directly from the dashboard without having to go through Stripe.
Create a bookmark.

Replay event from the bookmark.

Conclusion
Webhook debugging follows a consistent pattern regardless of the provider: verify the event was sent, confirm it arrived, check authentication, inspect processing logic, and review retry behavior. The systematic debugging flowchart above gives you a repeatable process for diagnosing any webhook issue.
For building resilient webhook handlers that fail gracefully, see Taking Control of Your Webhook Reliability. For understanding how delivery guarantees affect your debugging approach, see Webhook Delivery Guarantees. And for monitoring webhook health proactively (so you catch issues before your users do), see Metrics.
The Hookdeck CLI and Hookdeck Console are free tools that make webhook debugging significantly easier — try them to see how inspection, replay, and filtering work together to accelerate your debugging workflow.
FAQs
Why am I not receiving webhooks?
Common causes include: the provider URL is misconfigured (check the webhook URL in your provider's dashboard), DNS issues preventing resolution of your endpoint, a firewall or CDN blocking the provider's requests, your server is down or not listening on the correct port, or the provider requires a challenge/handshake that hasn't been completed.
How do I debug failed webhook deliveries?
Start by checking your webhook infrastructure logs for the delivery attempt — look at the HTTP status code, response body, and timing. With Hookdeck, inspect the event in the dashboard to see the full request/response for each delivery attempt. Check if the failure is a timeout, authentication error, or application error, then address the root cause.
Why are my webhooks timing out?
Webhook timeouts occur when your handler takes too long to respond. Most providers timeout after 5-30 seconds. The solution is to acknowledge the webhook immediately (return 200), then process the payload asynchronously via a queue or background job. Never do heavy processing (database writes, API calls, file operations) before responding.
How do I fix webhook signature verification errors?
Common causes: using the wrong signing secret (check your provider dashboard), parsing the body before verification (verify against the raw body, not parsed JSON), using the wrong encoding (Base64 vs hex), or a timing issue with timestamped signatures. Use the provider's SDK when available, as it handles encoding details correctly.
How do I handle duplicate webhook events?
Implement idempotent processing using a unique identifier from the event (such as a provider event ID or the x-hookdeck-eventid header). Before processing, check if the event has already been handled. Use database unique constraints or a Redis-based deduplication cache. See our guide to implementing webhook idempotency for detailed patterns.
What tools can I use to test and debug webhooks?
The Hookdeck CLI lets you receive webhooks on localhost with full request inspection, retry, and replay. The Hookdeck Console (console.hookdeck.com) provides a free web-based tool to inspect webhook requests. For provider-specific testing, most providers offer test webhook functionality in their dashboards. For a comparison of tools, see our best webhook testing tools guide.
Webhook infrastructure, managed for you
Hookdeck handles ingestion, delivery, observability, and error recovery — so you don't have to.