Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch from misaligned endpoint secret or raw body handling—verify secret matches live environment and use raw request body, not parsed JSON.

Stripe: webhook signature verification failing [2026 fix]

TL;DR

Cause: Your endpoint secret is either wrong, from the wrong environment, or your code is verifying against a parsed JSON body instead of the raw request stream.

Fix: Use the correct live/test secret for your environment and always verify against req.rawBody (not req.body), then parse after verification.

---

Real Console Error Messages

``` No signature header 'stripe-signature' found ```

``` Webhook signature verification failed. Webhook ID: we_xxxxxxxxxx ```

``` Signature verification failed. Possibly due to a timestamp outside the time window. ```

``` Invalid signature for payload. ```

``` Error: Unable to extract timestamp and signatures from header ```

---

Broken Code vs. Fixed Code

Problem 1: Using Parsed Body Instead of Raw Body

BROKEN ❌ ```javascript app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; try { // WRONG: req.body is already parsed JSON const event = stripe.webhooks.constructEvent( req.body, // ← This is the problem sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

FIXED ✅ ```javascript app.post( '/webhook', express.raw({type: 'application/json'}), // ← Raw body middleware (req, res) => { const sig = req.headers['stripe-signature']; try { // CORRECT: req.body is Buffer, not parsed const event = stripe.webhooks.constructEvent( req.body, // ← Now this is the raw request stream sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } } ); ```

Problem 2: Wrong Endpoint Secret (Test vs. Live)

BROKEN ❌ ```javascript const endpointSecret = 'whsec_test_1234567890'; // Always test?

const event = stripe.webhooks.constructEvent( body, sig, endpointSecret // ← Mismatched environment ); ```

FIXED ✅ ```javascript // Use correct secret for current environment const endpointSecret = process.env.NODE_ENV === 'production' ? process.env.STRIPE_WEBHOOK_SECRET_LIVE : process.env.STRIPE_WEBHOOK_SECRET_TEST;

const event = stripe.webhooks.constructEvent( body, sig, endpointSecret // ← Now matches webhook source ); ```

Problem 3: Parsing Body Before Verification

BROKEN ❌ ```javascript app.use(express.json());

app.post('/webhook', (req, res) => { const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( JSON.stringify(req.body), // ← Stringify of parsed = wrong hash sig, process.env.STRIPE_WEBHOOK_SECRET ); }); ```

FIXED ✅ ```javascript app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( req.body, // ← Buffer/stream, not stringified sig, process.env.STRIPE_WEBHOOK_SECRET ); // Parse AFTER verification const data = JSON.parse(req.body); }); ```

---

Step-by-Step Verification Checklist

1. Confirm your endpoint secret (Stripe Dashboard → Developers → Webhooks → click endpoint → "Signing secret") 2. Check environment variables loaded correctly: ```javascript console.log('Secret ends with:', process.env.STRIPE_WEBHOOK_SECRET?.slice(-4)); ``` 3. Inspect headers at runtime: ```javascript console.log('Stripe signature header:', req.headers['stripe-signature']); ``` 4. Verify middleware order — raw body parser must come *before* JSON parser 5. Test with Stripe CLI locally: ```bash stripe listen --forward-to localhost:3000/webhook stripe trigger payment_intent.succeeded ```

---

Still broken? Check these too

1. [Webhook endpoint DNS/network issues](/?guide=webhook-dns) — Stripe can't reach your URL if it's behind a firewall or uses self-signed SSL. Test with curl https://your-endpoint.com/webhook.

2. [Timestamp validation outside 5-minute window](/?guide=stripe-timestamp) — System clock drift causes verification to fail. Run ntpdate -s time.nist.gov on your server.

3. [Multiple webhook endpoints with same secret](/?guide=stripe-duplicate-webhooks) — If you have test and live endpoints sharing secrets, Stripe rejects requests. Create separate signing secrets per endpoint in the Dashboard.

---

Official Documentation

📖 [Stripe Webhooks Security - Official Docs](https://stripe.com/docs/webhooks/signatures)

📖 [Stripe Node.js SDK - constructEvent](https://stripe.com/docs/api/nodejs#construct_event)

---

Found a different variation? Drop it in the comments — including your framework (Next.js, FastAPI, etc.) and error message. We update this guide based on community reports.

🔥 0d
LIVE
PlanetScale rage spiking Vercel pricing complaints Railway gaining fast Supabase happiness rising Resend loved by devs PlanetScale rage spiking Vercel pricing complaints Railway gaining fast Supabase happiness rising
DEVELOPER PAIN RADAR // Loading...

Developers complain.
Opportunities appear.

We track what developers are struggling with today — and what opportunities that creates.

guides today
avg happiness
🔥 Pain
📖 Guides
🔭 Explore
👤 Mine
🔥 Pain Radar — rage scores today
↗ share
💡 Opportunity Feed — pain = market gap
📈 Tool Momentum
all scores →
📖 Latest Guide
all guides →
📖 All Guides
📊 Tool Scores
+ Submit
📰 Hacker News
➕ Submit a Tool
← back