How to Test Stripe Webhooks Locally
What Webhooks Does Stripe Send?
Stripe relies heavily on webhooks to notify your application about events that happen asynchronously — charges succeeding, subscriptions renewing, disputes opening, and dozens more. The most common events you will handle are payment_intent.succeeded, checkout.session.completed, invoice.paid, customer.subscription.updated, and charge.refunded.
Each webhook payload is a JSON object with a consistent structure: a top-level type field identifying the event, a data.object field containing the relevant Stripe resource, and metadata like created timestamp and livemode boolean. Stripe signs every webhook with a Stripe-Signature header so you can verify authenticity.
In development, the challenge is that Stripe needs to reach a publicly accessible URL. Your localhost server is invisible to the internet. This is where ReqPour comes in — it gives you a stable public endpoint that captures every request and relays it to your local machine in real time.
Configuring Stripe to Send Webhooks to ReqPour
First, create an endpoint in the ReqPour dashboard or via the CLI. You will get a URL like https://abc123.reqpour.com. Head to the Stripe Dashboard, navigate to Developers > Webhooks, and click "Add endpoint." Paste your ReqPour URL and select the events you want to receive.
For development, it is usually best to subscribe to all events so you can explore the payloads. In production you would narrow this down. Stripe will immediately send a test event you can verify in the ReqPour request inspector.
If you are using Stripe CLI for local testing already, ReqPour offers advantages: your endpoint URL is permanent across sessions, the dashboard gives you a visual inspector with search and filtering, and you can replay any request without re-triggering the event in Stripe.
Inspecting Stripe Webhooks in the Dashboard
Once Stripe starts sending events, open the ReqPour dashboard and select your endpoint. Each incoming request appears in real time with its HTTP method, path, headers, and full JSON body. You can expand any request to see the complete Stripe-Signature header, the event type, and the nested data object.
Use the search bar to filter by event type — for example, type "checkout.session" to see only checkout completions. The JSON viewer highlights keys and values with syntax coloring, and you can copy the entire payload with one click. This is invaluable when you are building your webhook handler and need to understand the exact shape of each event.
Relaying to Your Local Server
Start the ReqPour CLI relay to forward webhooks to your local development server:
npx reqpour login
npx reqpour relay --to http://localhost:3000/api/webhooks/stripeEvery request that hits your ReqPour endpoint is now forwarded to your local server. The CLI shows the HTTP method, path, response status, and latency for each relayed request. If your local server returns an error, you will see it immediately.
You can also use the dashboard to replay past requests. This is especially useful for testing idempotency — replay the same payment_intent.succeeded event multiple times and verify your handler does not create duplicate records.
Handling Stripe Webhooks in Your Code
Here is a typical Express.js handler that verifies the Stripe signature and processes the event:
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);const app = express();
app.post('/api/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.error('Signature verification failed:', err.message); return res.status(400).send('Webhook signature verification failed'); }
switch (event.type) { case 'payment_intent.succeeded': const paymentIntent = event.data.object; console.log('Payment succeeded:', paymentIntent.id); break; case 'checkout.session.completed': const session = event.data.object; console.log('Checkout completed:', session.id); break; default: console.log('Unhandled event type:', event.type); }
res.json({ received: true }); } ); ```
Note the use of express.raw() — Stripe signature verification requires the raw request body, not parsed JSON. This is a common pitfall. When testing with ReqPour, you can compare the raw body in the dashboard with what your handler receives to debug signature mismatches.
Tips for Stripe Webhook Development
Always verify webhook signatures in production. During development with ReqPour, you can temporarily skip verification to focus on your business logic, then add it back before deploying. Store the webhook signing secret from Stripe in an environment variable, never in your code.
Make your webhook handlers idempotent. Stripe may send the same event more than once, especially during network issues. Use the event ID to deduplicate — store processed event IDs in your database and skip duplicates. ReqPour's replay feature makes it easy to test this: replay the same event and confirm your handler recognizes the duplicate.
Return a 200 status quickly. Do heavy processing asynchronously — acknowledge the webhook, queue the work, and process it in the background. Stripe will retry on 4xx/5xx responses and eventually disable your endpoint if failures persist.
Related
Get started with ReqPour
Catch, inspect, and relay webhooks to localhost. Free to start, $3/mo for Pro.