DEV Community

Cover image for How to Debug Stripe Webhooks on Localhost (The Easy Way)
Ahmed Mandour
Ahmed Mandour

Posted on

How to Debug Stripe Webhooks on Localhost (The Easy Way)

If you've ever debugged a Stripe webhook, you know the pain. You set up ngrok, configure your endpoint in the Stripe dashboard, trigger a test payment, wait for the webhook to arrive... and then realize you need to add a log statement. So you trigger another payment. And another. And another.

Three hours later, you've triggered 47 test payments, your ngrok session has restarted twice (giving you a new URL each time), and you still haven't figured out why your code isn't handling the payment_intent.succeeded event correctly.

There's a better way.

The Problem with Traditional Webhook Debugging

The traditional approach to debugging webhooks has several pain points:

  • You can't reproduce the exact payload — Each test webhook has different IDs, timestamps, and sometimes slightly different data
  • Tunnel URLs change — Free ngrok sessions give you a new URL on restart, requiring you to update Stripe's dashboard
  • It's slow — Creating a test payment, waiting for the webhook, checking your logs, making a change, repeat
  • No breakpoint debugging — By the time you set a breakpoint, the webhook has already timed out

The core issue: You're debugging a race condition between your code and an external service. You need to control when the webhook arrives, but with traditional methods, you can't.

The Capture-and-Replay Approach

What if instead of triggering a new webhook every time, you could capture one and replay it as many times as you need?

That's the idea behind HookReplay. Here's how it works:

  1. Capture — Point Stripe at a HookReplay URL. When a webhook arrives, it's stored.
  2. Inspect — View the full payload, headers, and metadata in the dashboard.
  3. Replay — Send that exact webhook to your localhost, as many times as you need.

This means you trigger the test payment once, capture the webhook, and then replay it 100 times while you debug your code. Set breakpoints. Add log statements. Fix bugs. All without waiting for Stripe to send another event.

Step-by-Step: Debugging Stripe Webhooks

Let's walk through a complete example.

Step 1: Create an endpoint

Sign up at hookreplay.dev (free tier available). Create a workspace for your project, then create a webhook endpoint. You'll get a unique URL like:

https://hookreplay.dev/hook/abc123xyz
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Stripe

In the Stripe Dashboard, go to Developers → Webhooks → Add endpoint.

Paste your HookReplay URL and select the events you want to receive:

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • checkout.session.completed
  • ...or any other events you're working with

Step 3: Trigger a test webhook

You have two options:

  • Use Stripe's test event button — In the webhook details, click "Send test webhook"
  • Create a real test payment — Use Stripe's test mode with card 4242 4242 4242 4242

The webhook will be captured and appear in your dashboard.

Step 4: Install and connect the CLI

Install the HookReplay CLI:

npm install -g hookreplay
Enter fullscreen mode Exit fullscreen mode

Get an API key from your account settings, then connect:

$ hookreplay

● hookreplay> config api-key hr_xxxxxxxxxxxx
✓ API key saved!

● hookreplay> connect
✓ Connected! Waiting for replay requests...
Enter fullscreen mode Exit fullscreen mode

Step 5: Replay to localhost

In the dashboard, find the captured webhook and click Replay.

Enter your local endpoint URL (e.g., http://localhost:3000/api/stripe/webhook) and click Send.

● hookreplay> 
↳ Replaying payment_intent.succeeded to http://localhost:3000/api/stripe/webhook
✓ 200 OK (45ms)
Enter fullscreen mode Exit fullscreen mode

Debugging with Breakpoints

Here's where the magic happens. Because you control when the webhook is replayed, you can:

  1. Set a breakpoint in your webhook handler
  2. Start your server in debug mode
  3. Click "Replay" in HookReplay
  4. Step through your code at your own pace

No more racing against Stripe's timeout. No more adding console.log statements and re-triggering the event. Just normal, comfortable debugging.

Pro tip: Want to test what happens when a field is null or has an unexpected value? Edit the payload before replaying. Perfect for testing edge cases without waiting for them to happen organically.

Testing Edge Cases

One of the most powerful features is testing edge cases. With traditional webhook debugging, you're limited to whatever data Stripe sends you. With HookReplay, you can modify the payload before replaying.

Want to test:

  • What if the amount is zero? — Edit amount_received to 0
  • What if a field is missing? — Remove the field from the JSON
  • What if there are multiple line items? — Add more items to the array
  • What if the customer is null? — Set customer to null

This is especially useful for testing error handling. How does your code behave when Stripe sends unexpected data? Now you can find out without waiting for production bugs.

Handling Signature Verification

Stripe webhooks come with a signature header (Stripe-Signature) that you should verify in production. During development, you have options:

Option 1: Disable verification in development

// Node.js example
const event = process.env.NODE_ENV === 'production'
  ? stripe.webhooks.constructEvent(body, sig, webhookSecret)
  : JSON.parse(body); // Skip verification in development
Enter fullscreen mode Exit fullscreen mode

Option 2: Use Stripe's test mode webhook secret

When you use Stripe's "Send test webhook" button, it uses your test mode webhook secret. You can use this secret locally to verify signatures on test webhooks.

Important: Never disable signature verification in production. It protects against attackers sending fake webhook events to your endpoint.

Common Issues and How to Debug Them

"No such customer" error

Cause: You're using your live mode API key but the webhook was sent from test mode (or vice versa).

Debug: Inspect the webhook payload. Check if the IDs are from the right environment. Then check which API key your code is using.

Duplicate processing

Cause: You're not implementing idempotency. Stripe may send the same webhook multiple times.

Debug: Replay the same webhook 5 times in a row. Does your code handle it correctly? It should only process the first one and ignore duplicates.

Missing event types

Cause: Your webhook handler works for payment_intent.succeeded but fails silently for checkout.session.completed.

Debug: Capture both event types and replay them. Step through your code to see where the second event type gets dropped.

Summary

Debugging Stripe webhooks doesn't have to be painful. By capturing webhooks and replaying them on demand, you can:

  • Debug with breakpoints at your own pace
  • Test edge cases by editing payloads
  • Save hours of development time
  • Keep your sanity intact

The capture-and-replay approach works for any webhook, not just Stripe. GitHub, Shopify, Twilio, Paddle — if it sends webhooks, you can capture and replay them.


Originally published at hookreplay.dev/blog

Top comments (0)