Stripe webhooks · Node.js
How to verify Stripe webhook signatures in Node.js
Stripe signs each webhook delivery. This guide shows how to verify that signature with the official Node SDK, preserve the request body, and avoid the mistakes that cause valid events to fail.
By the Hookflo team · Updated September 29, 2026 · 7 minute read
Why Stripe webhook signature verification matters
A webhook URL is an HTTP endpoint. Without verification, an attacker who discovers the URL could send a forged request that looks like a successful payment. Stripe signs webhook events so your application can verify that a delivery was generated by Stripe and has not been changed.
The signature is not a replacement for idempotency or business validation. Verify the request, then process event types deliberately and make handlers safe to run more than once.
1. Get the endpoint signing secret
In the Stripe Dashboard, open Developers → Webhooks, select the endpoint, and reveal its signing secret. The value begins with whsec_. Store it in an environment variable and keep it out of source control.
STRIPE_WEBHOOK_SECRET=whsec_...Local Stripe CLI forwarding uses a separate endpoint secret. Use the secret printed by stripe listen for local events, and use the Dashboard endpoint secret for that deployed endpoint.
2. Preserve the raw request body
Stripe verifies the request body bytes it signed. If middleware parses and reserializes JSON first, whitespace or formatting can change and the signature check will fail. Verify before parsing the body yourself.
Express
const express = require('express');
const Stripe = require('stripe');
const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// Register the raw-body webhook route before express.json().
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
return res.json({ received: true, type: event.type });
} catch (error) {
return res.status(400).send('Invalid Stripe webhook signature');
}
});
app.use(express.json());Next.js App Router
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
const rawBody = await request.text();
const signature = request.headers.get('stripe-signature');
if (!signature) {
return new Response('Missing Stripe-Signature', { status: 400 });
}
try {
const event = stripe.webhooks.constructEvent(
rawBody,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
return Response.json({ received: true, type: event.type });
} catch {
return new Response('Invalid Stripe webhook signature', { status: 400 });
}
}3. Test the endpoint
Run Stripe CLI forwarding against your local endpoint, then trigger a test event:
stripe listen --forward-to localhost:3000/webhook
stripe trigger checkout.session.completedUse test-mode events while developing, and confirm that the endpoint secret matches the CLI listener. In production, check the Stripe Dashboard delivery attempt when an endpoint returns a non-2xx response.
Common signature verification mistakes
- Parsing JSON too early. Keep raw bytes until verification completes.
- Using the wrong secret. Endpoint secrets differ by environment and endpoint.
- Confusing API keys and webhook secrets. Verification uses the endpoint signing secret, not a Stripe API key.
- Ignoring duplicate deliveries. Record processed event IDs and make side effects idempotent.
- Treating all non-signature failures alike. Separate verification errors from downstream database or business-logic errors.
See Stripe webhook deliveries before a customer reports a problem
Signature verification protects your endpoint from forged requests. Monitoring helps your team see delivery and verification issues in context. Hookflo records Stripe webhook events, verifies the endpoint signature, and can route alerts to Slack or email.