Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch due to raw body not preserved or wrong signing secret. Ensure raw request body is passed to signature verification, not parsed JSON.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: Your framework is parsing the request body before Stripe's signature verification runs, destroying the raw bytes needed for cryptographic validation. Fix: Pass the raw unparsed request body to stripe.Webhook.constructEvent() instead of parsed JSON.

---

Real Console Error Messages

``` Error: No signatures found matching the expected signature for payload. Managed signing secret mismatch. ```

``` StripeSignatureVerificationError: No signatures found matching the expected signature for payload. Possible reasons: (1) webhook secret is incorrect, (2) payload was not sent as received ```

``` Error: Invalid signature: sig_xxx does not match computed signature. Webhook endpoint received event but verification failed at timestamp validation. ```

``` TypeError: Cannot read property 'rawBody' of undefined at verifyWebhookSignature (/app/webhooks.js:42:15) ```

``` Stripe webhook error: Signature verification failed. Computed sig=sha256=abc... but received sig=sha256=xyz... Payload mismatch detected. ```

---

The Problem: Side-by-Side Code Fix

❌ BROKEN CODE (Express.js)

```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const app = express();

// WRONG: Body parser runs BEFORE webhook handler app.use(express.json());

app.post('/webhooks/stripe', (req, res) => { const sig = req.headers['stripe-signature']; try { // BROKEN: req.body is already parsed JSON, raw bytes lost const event = stripe.webhooks.constructEvent( req.body, // ← This is a JavaScript object, not raw bytes sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({ received: true }); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED CODE (Express.js)

```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const app = express();

// CORRECT: Raw body middleware runs FIRST, ONLY for Stripe webhooks app.post( '/webhooks/stripe', express.raw({ type: 'application/json' }), // ← Raw body, not parsed (req, res) => { const sig = req.headers['stripe-signature']; try { // FIXED: req.body is raw Buffer, signature verification works const event = stripe.webhooks.constructEvent( req.body, // ← This is Buffer with original bytes sig, process.env.STRIPE_WEBHOOK_SECRET ); // Handle event if (event.type === 'payment_intent.succeeded') { console.log('Payment succeeded:', event.data.object.id); } res.json({ received: true }); } catch (err) { console.error('Webhook signature verification failed:', err.message); res.status(400).send(Webhook Error: ${err.message}); } } );

// Parse JSON for OTHER routes app.use(express.json()); ```

❌ BROKEN CODE (Next.js API Route)

```javascript // pages/api/webhooks/stripe.js - BROKEN export default async (req, res) => { const sig = req.headers['stripe-signature']; const body = JSON.stringify(req.body); // ← Re-stringifying loses original try { const event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { return res.status(400).json({ error: err.message }); } }; ```

✅ FIXED CODE (Next.js API Route)

```javascript // pages/api/webhooks/stripe.js - FIXED export const config = { api: { bodyParser: false, // ← Disable automatic body parsing }, };

export default async (req, res) => { const sig = req.headers['stripe-signature']; // Read raw body as Buffer const chunks = []; for await (const chunk of req) { chunks.push(chunk); } const rawBody = Buffer.concat(chunks); try { const event = stripe.webhooks.constructEvent( rawBody, // ← Original bytes preserved sig, process.env.STRIPE_WEBHOOK_SECRET ); res.status(200).json({ received: true }); } catch (err) { res.status(400).json({ error: err.message }); } }; ```

---

Why This Happens

Stripe signs the exact raw request body bytes. When your framework automatically parses JSON:

1. Original bytes → parsed JavaScript object 2. Object → re-serialized (different whitespace/order) 3. Signature hash = different result 4. Verification fails

The cryptographic signature is only valid for the exact bytes Stripe sent. Any transformation breaks it.

---

Still Broken? Check These Too

1. Wrong webhook secret: Verify STRIPE_WEBHOOK_SECRET in .env matches Stripe Dashboard → Developers → Webhooks. Copy the entire whsec_xxx string, not your signing secret.

2. Multiple body parsers: If using app.use(express.json()) globally, it runs before route handlers. Move it AFTER webhook routes or create a separate app instance. See [debugging middleware order](/?guide=express-middleware-order) for details.

3. Running old Stripe SDK: stripe npm package < v8 has different API. Verify version: npm list stripe. Update with npm install stripe@latest. Version behavior differs; we're testing against v14+ but cannot guarantee older versions.

---

Testing Your Fix

```bash

Use Stripe CLI to send test event

stripe listen --forward-to localhost:3000/webhooks/stripe stripe trigger payment_intent.succeeded ```

You should see: Webhook signature verification failed: No signatures found... → disappeared after applying fix.

---

Related Resources

  • [Stripe webhook security best practices](/?guide=stripe-webhook-security)
  • [Express.js body parsing gotchas](/?guide=express-body-parser-issues)
  • Official: [Stripe Webhook Signature Verification Docs](https://stripe.com/docs/webhooks/signatures)
  • ---

    Found a different variation? Drop it in the comments.

    🔥 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