How to receive, verify, test, and monitor Stripe webhooks — step by step with code examples.
Last updated March 2026. Covers Stripe API version 2025-12 and later.
Stripe uses webhooks to notify your application when events happen in your account — a payment succeeds, a subscription renews, a dispute is opened. Instead of polling the Stripe API, your app receives an HTTP POST with a JSON payload the moment something occurs.
Common Stripe webhook event types:
Create an HTTP POST route that accepts Stripe's JSON payloads. Here's a minimal Node.js Express example:
const express = require('express');
const app = express();
// Important: use raw body for signature verification
app.post('/webhook/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const event = JSON.parse(req.body);
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('Payment succeeded:', paymentIntent.id);
// Fulfill the order, update DB, send receipt...
break;
case 'invoice.payment_failed':
console.log('Invoice payment failed:', event.data.object.id);
// Notify customer, retry logic...
break;
default:
console.log('Unhandled event type:', event.type);
}
res.status(200).json({ received: true });
}
);
app.listen(3000, () => console.log('Webhook server on port 3000'));
Key points: always return a 200 response quickly. Process heavy work asynchronously (queue, background job). Stripe retries on non-2xx responses for up to 3 days.
Never trust a webhook payload without verifying its signature. Stripe signs every webhook with your endpoint secret (starts with whsec_). Here's how:
const stripe = require('stripe')('sk_...');
const endpointSecret = 'whsec_...';
app.post('/webhook/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body, // raw body, NOT parsed JSON
sig,
endpointSecret
);
} catch (err) {
console.error('Signature verification failed:', err.message);
return res.status(400).send('Webhook signature verification failed');
}
// Event is verified — safe to process
console.log('Verified event:', event.type);
res.status(200).json({ received: true });
}
);
Critical: pass the raw request body to constructEvent, not parsed JSON. If you use express.json() middleware globally, it will break verification. Use express.raw({ type: 'application/json' }) on the webhook route specifically.
During development you need a public URL that Stripe can reach. Here's the fastest path:
payment_intent.succeeded, checkout.session.completed).stripe trigger payment_intent.succeeded from the Stripe CLI.For local development, use sdnbros tunnel to forward webhooks from your SDNBros endpoint directly to localhost:3000.
In production, webhook failures are silent killers — a missed invoice.payment_failed event means a churned customer you never knew about. Set up proper monitoring:
Create a free SDNBros account to start monitoring your Stripe webhooks in production.
| Error | Cause | Fix |
|---|---|---|
| Signature verification failed | Using parsed JSON body instead of raw body, or wrong endpoint secret | Use express.raw() on the webhook route. Check your whsec_ secret matches the endpoint in Stripe Dashboard. |
| Webhook timeout (no response) | Endpoint takes longer than 20 seconds to respond | Return 200 immediately and process the event asynchronously in a background worker. |
| Duplicate events received | Stripe retries on non-2xx or timeout. Your handler processes the same event twice. | Store the event ID (evt_...) and check for duplicates before processing. Make your handler idempotent. |
| 400 Bad Request from Stripe | Endpoint URL is unreachable or returns HTML instead of JSON | Verify the URL is correct and publicly accessible. Check for WAF or proxy rules blocking Stripe's IPs. |
| Events not arriving | Wrong events selected in Stripe Dashboard, or endpoint disabled after too many failures | Check Stripe Dashboard > Webhooks > your endpoint. Re-enable if disabled. Verify selected event types. |
Create a public endpoint and inspect Stripe events. No signup is needed for the tester.
Open Webhook Tester Monitor in ProductionRelated guides: What is a Webhook? · Webhook vs API · Webhook Security · Retry Strategies · GitHub · Shopify · Slack
More: What is a Webhook? | Webhook Security Guide | Stripe Integration Guide | Compare Tools