How to debug Stripe webhooks (signature, body, replay)
If your Stripe webhook handler is broken, the problem is almost always one of three things: wrong endpoint URL, wrong signature verification, or wrong event parsing. This walkthrough fixes each one using RequestBin as the inspection + replay layer.
You wired a Stripe webhook to your handler. The Stripe Dashboard says the event was delivered. Your code never ran. Or it ran but returned 500. Or it ran but the event is parsed wrong. Now what?
Three failure modes, three fixes. Each one easier when you can see the raw request Stripe actually sent.
Setup: get a bin URL
Sign up at requestbin.net (no credit card on FREE). Create a bin → copy the URL → paste into your Stripe Dashboard → Developers → Webhooks → Add endpoint.
Now every Stripe event also hits the bin where you can inspect it.
Failure mode 1: signature verification mismatch
Symptom: your handler logs "Stripe-Signature header missing" or "signature does not match".
Open the bin in RequestBin, click the captured request, look at the headers. You should see:
Stripe-Signature: t=1716220800,v1=5257a869e7eceb...Three things to check:
- Is your handler reading the raw body? Signature verification needs the exact bytes Stripe sent. If you're using Express, that means
express.raw({ type: 'application/json' })for the webhook route, NOT the globalexpress.json()middleware. The latter parses and reserialises the body, which breaks the HMAC. - Is your webhook secret right? The
STRIPE_WEBHOOK_SECRETenv var must match the signing secret shown in the Stripe Dashboard for THIS specific endpoint. There's a different secret per endpoint. - Is the timestamp drifting? Stripe rejects signatures older than 5 minutes by default. If your server clock is off, signatures fail intermittently.
Failure mode 2: handler never runs
Symptom: Stripe Dashboard shows the delivery succeeded (200 from your endpoint) but your handler logs nothing.
If the bin shows the request landed but your local code didn't, you're either:
- Not pointed at the right URL — check your
.envmatches Stripe's endpoint URL - Running a different process than you think — kill all node processes and start fresh
- Using the wrong route — Stripe POSTs to the URL exactly as configured, no path rewriting
Failure mode 3: event parsing wrong
Symptom: handler runs, but pulls the wrong field from the event payload.
Open the bin, look at the Body tab. Stripe payloads look like:
{
"id": "evt_3OabcDEFGH",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_3OabcDEFGH",
"amount": 2000
}
}
}The PaymentIntent ID is at data.object.id, not id. The id at the top is the EVENT id, not the resource id. Mixing them up will silently break your idempotency logic.
Replay an event into your local code
Once you've caught a real event in the bin, click Replay on the inspector — RequestBin sends the same request again to your endpoint. Useful when your local code crashes mid-handler and you want to re-run the same event after fixing the bug. Free on every plan.
Replay with edits
If you want to mutate the payload first (e.g. test what happens with a malformed amount), use Replay & Edit — opens the request in the HTTP Requester where you can change headers, body, query params before sending. This is a Pro feature ($12/mo).
Alternative: Stripe CLI
Stripe ships a CLI that can forward events to localhost. Use that for the "send fake events at my local dev server" loop. Use RequestBin for the "something's wrong in production and I need to see the actual bytes Stripe is sending" loop.
Going further
- Stripe Webhook Tester landing — preconfigured headers + sample payload
- Replay docs
- The bin URL works for any HTTP source — Twilio, Slack, GitHub, your own integration tests, all of them