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