The Stripe CLI lets you forward webhook events to your local development server without deploying to a public URL. Run 'stripe listen --forward-to localhost:3000/webhook' to start receiving events locally. The CLI provides a temporary webhook signing secret and lets you trigger specific events for testing. This guide covers installation, event forwarding, triggering test events, and debugging webhook issues.
Testing Stripe Webhooks on Your Local Machine
During development, your webhook endpoint is running on localhost which Stripe cannot reach. The Stripe CLI bridges this gap by establishing a WebSocket connection to Stripe and forwarding events to your local server. It also provides a temporary webhook signing secret (whsec_) for signature verification. This is the recommended way to develop and test webhook handlers.
Prerequisites
- Node.js 18 or later with your webhook server code ready
- A Stripe account (test mode)
- Homebrew (macOS), scoop (Windows), or apt (Linux) for CLI installation
Step-by-step guide
Install the Stripe CLI
Install the Stripe CLI
Install the CLI using your platform's package manager.
1# macOS2brew install stripe/stripe-cli/stripe34# Windows (scoop)5scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git6scoop install stripe78# Linux (Debian/Ubuntu)9curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg10echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list11sudo apt update && sudo apt install stripe1213# Verify installation14stripe versionExpected result: The Stripe CLI is installed and outputs its version number.
Authenticate the CLI with your Stripe account
Authenticate the CLI with your Stripe account
Log in to connect the CLI to your Stripe account. This opens a browser for authentication.
1# Log in to Stripe (opens browser for OAuth)2stripe login34# Or use an API key directly (useful for CI)5stripe login --api-key sk_test_yourTestKeyExpected result: The CLI authenticates with your Stripe account and confirms the connection.
Forward webhooks to your local server
Forward webhooks to your local server
Start the webhook listener to forward all Stripe events to your local endpoint. The CLI provides a temporary signing secret.
1# Forward all events to your local server2stripe listen --forward-to localhost:3000/webhook34# Output:5# Ready! Your webhook signing secret is whsec_abc123...6# (use this secret for signature verification in your local server)78# Forward only specific events:9stripe listen --forward-to localhost:3000/webhook \10 --events payment_intent.succeeded,payment_intent.payment_failed,customer.subscription.createdExpected result: The CLI connects to Stripe and begins forwarding events to localhost:3000/webhook. A signing secret is displayed.
Update your server to use the CLI signing secret
Update your server to use the CLI signing secret
Use the whsec_ secret from the CLI output in your webhook endpoint for local development.
1// .env (for local development)2STRIPE_WEBHOOK_SECRET=whsec_abc123... // From 'stripe listen' output34// Your webhook handler stays the same:5const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);67app.post('/webhook',8 express.raw({ type: 'application/json' }),9 (req, res) => {10 const sig = req.headers['stripe-signature'];11 let event;12 try {13 event = stripe.webhooks.constructEvent(14 req.body,15 sig,16 process.env.STRIPE_WEBHOOK_SECRET // Uses CLI secret locally17 );18 } catch (err) {19 return res.status(400).send(`Webhook Error: ${err.message}`);20 }21 console.log('Event received:', event.type);22 res.json({ received: true });23 }24);Expected result: Webhook signature verification works with the CLI-provided signing secret.
Trigger test events from the CLI
Trigger test events from the CLI
Use 'stripe trigger' to fire specific webhook events for testing. This creates real test objects in your Stripe test account.
1# Trigger a payment_intent.succeeded event2stripe trigger payment_intent.succeeded34# Trigger a subscription lifecycle5stripe trigger customer.subscription.created67# Trigger a dispute8stripe trigger charge.dispute.created910# Trigger an invoice payment failure11stripe trigger invoice.payment_failed1213# Trigger a checkout session completion14stripe trigger checkout.session.completed1516# List all available trigger events17stripe trigger --listExpected result: Each command creates test objects in Stripe and fires the corresponding webhook event to your local server.
Debug webhook delivery issues
Debug webhook delivery issues
The CLI shows real-time event delivery status. Use it to debug signature verification failures and handler errors.
1# The CLI shows delivery status for each event:2# --> payment_intent.succeeded [evt_1ABC] -> localhost:3000/webhook [200]3# --> payment_intent.payment_failed [evt_2DEF] -> localhost:3000/webhook [400]45# Replay a specific event6stripe events resend evt_1ABCxyz78# View recent events in your account9stripe events list --limit 101011# Get details of a specific event12stripe events retrieve evt_1ABCxyz1314# Common issues and fixes:15# 400 error → Signature verification failing → Check STRIPE_WEBHOOK_SECRET matches CLI output16# Connection refused → Server not running → Start your server on port 300017# Timeout → Handler too slow → Return 200 immediately, process asyncExpected result: You can see event delivery status, replay events, and diagnose issues in real time.
Complete working example
1require('dotenv').config();2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);3const express = require('express');4const app = express();56// Webhook endpoint7app.post('/webhook',8 express.raw({ type: 'application/json' }),9 (req, res) => {10 const sig = req.headers['stripe-signature'];11 let event;1213 try {14 event = stripe.webhooks.constructEvent(15 req.body,16 sig,17 process.env.STRIPE_WEBHOOK_SECRET18 );19 } catch (err) {20 console.error('Signature verification failed:', err.message);21 console.error('Tip: Use the whsec_ from "stripe listen" output');22 return res.status(400).send(`Webhook Error: ${err.message}`);23 }2425 console.log(`\nReceived: ${event.type} (${event.id})`);2627 switch (event.type) {28 case 'payment_intent.succeeded':29 console.log(' Amount:', event.data.object.amount);30 break;31 case 'payment_intent.payment_failed':32 console.log(' Error:', event.data.object.last_payment_error?.message);33 break;34 case 'customer.subscription.created':35 console.log(' Sub ID:', event.data.object.id);36 break;37 case 'charge.dispute.created':38 console.log(' Dispute:', event.data.object.id);39 break;40 default:41 console.log(' (unhandled event type)');42 }4344 res.json({ received: true });45 }46);4748app.use(express.json());4950const PORT = process.env.PORT || 3000;51app.listen(PORT, () => {52 console.log(`Webhook test server on port ${PORT}`);53 console.log('\nSteps:');54 console.log('1. Run: stripe listen --forward-to localhost:' + PORT + '/webhook');55 console.log('2. Copy the whsec_ secret to your .env file');56 console.log('3. Restart this server');57 console.log('4. Run: stripe trigger payment_intent.succeeded');58});Common mistakes when testing webhooks locally for Stripe
Why it's a problem: Using the Dashboard webhook secret instead of the CLI's whsec_ for local testing
How to avoid: The CLI generates its own signing secret. Use the whsec_ displayed when you run 'stripe listen' in your local .env.
Why it's a problem: Forgetting to start the webhook server before running stripe listen
How to avoid: Start your server first (node server.js), then run stripe listen to forward events to it.
Why it's a problem: Closing the stripe listen terminal session while testing
How to avoid: Keep the stripe listen process running in a separate terminal. Events stop forwarding when you close it.
Why it's a problem: Not restarting the server after updating the webhook secret in .env
How to avoid: dotenv reads .env at startup. Restart your server after changing STRIPE_WEBHOOK_SECRET.
Best practices
- Keep stripe listen running in a dedicated terminal during development
- Use --events flag to filter only the events you are testing to reduce noise
- Use stripe trigger to test each event handler individually
- Use separate .env files for local (CLI whsec_) and production (Dashboard whsec_) webhook secrets
- Test error scenarios: trigger payment_intent.payment_failed and charge.dispute.created
- Run stripe events list to see recent events when debugging issues
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
How do I test Stripe webhooks on my local machine? I need to forward events to localhost without deploying. Show me how to use the Stripe CLI for webhook testing.
Help me set up local Stripe webhook testing. I need to install the Stripe CLI, forward events to my local Node.js server, trigger test events, and debug signature verification. Show me the complete workflow.
Frequently asked questions
Do I need a public URL for local webhook testing?
No. The Stripe CLI creates a WebSocket tunnel to forward events to your local server. No public URL, ngrok, or port forwarding needed.
Is the CLI webhook secret different from the Dashboard secret?
Yes. The CLI generates a temporary signing secret each time you run stripe listen. Use the CLI secret for local development and the Dashboard secret for production.
Can I trigger custom webhook events?
stripe trigger supports common event types. For custom scenarios, create the objects via the API (e.g., create a PaymentIntent and confirm it) and the webhook will fire naturally.
How do I test webhook retries locally?
Return a non-2xx status from your handler. The CLI will show the failed delivery but does not automatically retry like Stripe's production infrastructure does.
Can multiple developers share one stripe listen session?
No. Each developer should run their own stripe listen session forwarding to their local server. Events from one session will not reach other sessions.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation