Debug Stripe webhooks locally

Guides / Providers

Stripe signs each webhook with Stripe-Signature: t=<timestamp>,v1=<HMAC-SHA256>, computed over the timestamp and the raw body. Stripe's libraries reject signatures more than five minutes old. That makes captured Stripe events awkward to replay by hand: the body must be byte-exact and the timestamp must be fresh.

Receive Stripe events on localhost

hwcli listen payments --forward :3000/webhooks/stripe --include-sensitive

Add the endpoint URL in Stripe's Dashboard → Developers → Webhooks. --include-sensitive delivers the Stripe-Signature header, so your handler can run stripe.webhooks.constructEvent as it would in production. Live deliveries arrive within seconds, so their signatures are still fresh.

Replay with a fresh signature

Save the endpoint's signing secret (whsec_…) in Endpoint settings → Signature. HookWatcher stores it encrypted and never shows it again. Then:

hwcli replay evt_93df18ab --resign
hwcli replay evt_93df18ab --set data.object.amount_received=0 --resign

Each replay gets a new timestamp and a valid v1 signature over the body that is actually sent. That includes modified bodies, so you can test edge cases without disabling verification in your code.

Things to test for Stripe

  • Duplicates: Stripe can deliver an event more than once. --duplicate 3, and key on event.id.
  • Ordering: payment_intent.succeeded can arrive before payment_intent.created. Replay them in reverse.
  • Signature rejection: --invalid-signature and --remove-signature must get a 400.
  • Raw bodies: if your framework parses JSON before your handler, verification fails on every event. Exact replays show this immediately.

Create an endpoint and capture your first webhook. No signup needed.

Start Free