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:
- Capture — Point Stripe at a HookReplay URL. When a webhook arrives, it's stored.
- Inspect — View the full payload, headers, and metadata in the dashboard.
- 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
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.succeededpayment_intent.payment_failedcheckout.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
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...
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)
Debugging with Breakpoints
Here's where the magic happens. Because you control when the webhook is replayed, you can:
- Set a breakpoint in your webhook handler
- Start your server in debug mode
- Click "Replay" in HookReplay
- 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_receivedto0 - 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
customertonull
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
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)