How to Test Stripe Webhooks Without Deploying to Production
Source: Dev.to
Approach 1: Stripe CLI
The official way is to install the Stripe CLI, log in, and forward events to your local server:
stripe login
stripe trigger payment_intent.succeeded
Pros
- Quick setup.
Cons
- Mock events contain generic data (placeholder customer IDs, amounts).
- The tunnel dies when you close the terminal.
- Each restart generates a new signing secret, breaking signature verification until you update your
.env. - If your handler returns a 500, the CLI output doesn’t clearly show the error.
Approach 2: ngrok
Expose your local server directly with ngrok:
ngrok http 3000
- Paste the generated public URL into Stripe’s webhook settings.
- You receive real events from test‑mode payments, which is better than mock data.
Drawbacks
- The free URL changes each session, requiring you to update Stripe’s dashboard repeatedly.
- If your handler crashes mid‑request, the webhook is lost; you must wait for Stripe’s exponential backoff retries or manually reconstruct the payload.
- Closing ngrok erases the request history.
Approach 3: Capture First, Process Later
Instead of pointing Stripe at your local server, point it at a persistent endpoint that captures and stores every webhook. Then replay the captured payloads to your localhost when you’re ready.
Create an endpoint
curl -X POST https://hooklab-webhook-testing-and-debugging.p.rapidapi.com/api/v1/endpoints
The response includes a public URL, e.g.:
https://hooklab-webhook-testing-and-debugging.p.rapidapi.com/hook/ep_V1StGXR8_Z5j
Paste this URL into Stripe’s webhook settings.
Send a test payment
Use the Stripe Dashboard with the test card 4242 4242 4242 4242. Stripe sends the webhook, and HookLab captures it.
Inspect the captured request
curl https://hooklab-webhook-testing-and-debugging.p.rapidapi.com/api/v1/endpoints/ep_V1StGXR8_Z5j/requests
The response shows full headers, body, Stripe-Signature, timestamp, etc., eliminating the need for console‑log debugging.
Replay to your local server
curl -X POST https://hooklab-webhook-testing-and-debugging.p.rapidapi.com/api/v1/replay \
-d '{"url":"http://localhost:3000/api/webhooks/stripe"}'
The replay uses the same headers, body, and HTTP method, sending the request straight to your handler. If it crashes, fix the bug and replay again—no waiting for Stripe’s retry logic.
Why Capture‑and‑Replay Is Worth It
The three most common Stripe webhook bugs become easier to spot:
- Wrong event types – You might handle
charge.succeededwhile Stripe sendspayment_intent.succeeded. Capturing a checkout flow reveals every event Stripe fires, in order. - Data‑structure surprises – Accessing
event.data.object.customer.emailfails ifcustomeris a string ID rather than an expanded object. Real captured payloads show the exact structure before you write the handler. - Unexpected fields – Occasionally Stripe adds new fields; having the real payload lets you adapt quickly.
Gotchas Everyone Encounters
- Return
200 OKimmediately. Stripe expects a response within 5–10 seconds. Perform heavy processing asynchronously; otherwise Stripe treats the delivery as failed and retries, causing duplicate events. - Test with real test‑mode transactions. The
stripe triggercommand is fine for checking endpoint reachability, but your integration isn’t truly tested until you process webhooks from actual test checkouts, as the payloads differ in subtle but important ways.
Getting Started with HookLab
HookLab offers a free tier on RapidAPI (100 calls/day and 3 endpoints), which is sufficient for most Stripe integration testing.
What’s your webhook testing setup? Feel free to share your approach in the comments.