Capture and replay
Record real deliveries once, re-run them any time.
dev is a catcher that sits between your tunnel and your app. Point the tunnel at it instead of at the app:
provider -> ngrok http 3999 -> next-webhooks dev :3999 -> your app :3000
|
.webhooks/captures.jsonlRequests are proxied through with their path intact and your app's real response goes back to the provider, so retries and status codes behave exactly as if the tunnel pointed straight at the app. The difference is that every delivery is now recorded: headers plus the exact raw body, one JSON line each. --capture-only skips the proxying and answers 200 itself.
Add .webhooks/ to your .gitignore. Captures are real payloads and can contain real customer data.
npx next-webhooks dev
npx next-webhooks dev --port 4000 --forward http://localhost:3001
npx next-webhooks dev --capture-onlyReplay
replay re-sends captured deliveries to your app. It cannot simply resend the original request: the original signature was computed with the sender's secret, not your local one, and providers with replay protection reject timestamps older than a few minutes. So replay re-signs the captured raw body with your local secret and a fresh timestamp. Non-signature headers carry over unchanged.
npx next-webhooks replay
npx next-webhooks replay --provider stripe --type invoice.paid --last 1
npx next-webhooks replay --fresh-idsDuplicates and fresh ids
A replayed event carries its original id, so a route with idempotency enabled acknowledges it as a duplicate without running the handler. That is correct behavior, but when you want the handler to actually run, --fresh-ids rewrites event ids before signing.
The workflow this unlocks
Run dev, point ngrok at it, and do one real end-to-end flow: a test purchase, a repo push, a Slack mention. Every delivery involved is now on disk. From then on, replay re-runs the whole flow against your handler in seconds, with no tunnel, no dashboard, and no network. And when a real delivery exposes a bug, you replay that one delivery repeatedly while you debug, instead of hoping the provider sends it again.