Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch occurs when endpoint secret is wrong or request body is altered. Verify exact secret from Dashboard and ensure raw request body is passed to verification.

Stripe: webhook signature verification failing [2026 fix]

TL;DR

Cause: Your webhook endpoint secret is mismatched, or the request body was parsed/modified before signature verification.

Fix: Use the exact secret from Stripe Dashboard's Webhook Settings, verify raw request body is passed to constructEvent(), and ensure middleware order doesn't modify the body.

---

Exact Console Error Messages

``` Error: No signatures found matching the expected signature for payload. at constructEvent (/node_modules/stripe/lib/resources/WebhookEndpoints.js:92:15) at Object.constructEvent [as default] (/app/webhooks/stripe.js:15:8) ```

``` error: Webhook signature verification failed: invalid signature timestamp=1672531200, signatures=["v1=abcd1234..."], secret=whsec_live_xxxx ```

``` Stripe.error.StripeSignatureVerificationError: No signatures found matching the expected signature for payload. at verifySignatureHeaders (/node_modules/stripe/lib/WebhookEndpoint.js:118:22) ```

``` Error: Webhook endpoint received request with mismatched signature Expected: v1=xyz789 Received: v1=abc123 Timestamp: 1672531234567 ```

``` TypeError: Cannot read property 'id' of undefined at webhook handler (Signature verification passed but event object is null) ```

---

Broken Code vs. Fixed Code

❌ BROKEN: Using parsed JSON body

```javascript // app.js const bodyParser = require('body-parser'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.use(bodyParser.json());

app.post('/webhook', (req, res) => { // WRONG: body is already parsed const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent( req.body, // ← PROBLEM: this is Object, not raw string sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED: Using raw request body before parsing

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

// Apply JSON parser AFTER webhook route app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent( req.body, // ← CORRECT: raw Buffer from express.raw() sig, process.env.STRIPE_WEBHOOK_SECRET ); // Now safe to handle event if (event.type === 'payment_intent.succeeded') { console.log('Payment successful:', 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}); } });

// NOW apply body parser for other routes app.use(express.json()); ```

Environment Variable Verification

```bash

❌ WRONG: Using publishable key or incorrect secret

STRIPE_WEBHOOK_SECRET=pk_live_xxxx # NEVER use publishable key STRIPE_WEBHOOK_SECRET=whsec_test_xxxx # test key in production

✅ CORRECT: Exact secret from Dashboard → Webhooks → Click endpoint

STRIPE_WEBHOOK_SECRET=whsec_live_abcdef1234567890 # Copy from Dashboard exactly ```

---

Why This Happens

1. Middleware order matters: bodyParser.json() or express.json() converts raw request body to JavaScript object. Stripe's signature verification requires the exact raw bytes that were signed.

2. Wrong environment variable: Copy-pasting the wrong secret from Dashboard (test vs. live, or wrong endpoint).

3. Request body modification: Logging middleware, authentication middleware, or custom parsers that touch req.body before webhook handler.

4. Version confusion: Stripe.js v3+ changed signature verification behavior. [We've documented the Node SDK update process](/?guide=stripe-sdk-migration).

---

Still broken? Check these too

1. Webhook secret mismatch between environments – Verify you copied the secret for the correct environment (test vs. live) and correct endpoint URL from Stripe Dashboard.

2. Request signing timestamp too old – Stripe rejects signatures older than 5 minutes by default. Check server time sync: date && curl -I https://www.stripe.com/ to verify server clock.

3. Raw body middleware not applied correctly – If using frameworks like NestJS or Express with custom middleware, ensure express.raw({type: 'application/json'}) wraps the webhook route before any JSON parsing. [See middleware ordering guide](/?guide=express-middleware-order).

---

Verification Checklist

  • [ ] Secret starts with whsec_ (not sk_, pk_, or rk_)
  • [ ] Using express.raw() on webhook route only, not globally
  • [ ] No JSON parsing middleware before webhook handler
  • [ ] Webhook endpoint URL in Stripe Dashboard matches your actual route
  • [ ] Server time is synced (within 5 minutes of NTP)
  • [ ] Signature header present: stripe-signature in request headers
  • ---

    Official Resources

  • [Stripe Webhooks Documentation](https://stripe.com/docs/webhooks)
  • [Stripe.js Node.js Library - Webhook Signature Verification](https://github.com/stripe/stripe-node#webhook-signing)
  • [Stripe Dashboard - Webhook Endpoints](https://dashboard.stripe.com/webhooks)
  • ---

    Found a different variation? Drop it in the comments – Stripe's webhook auth behavior varies by SDK version and framework. Help us build a complete troubleshooting resource.

    🔥 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