Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch due to raw body tampering or wrong endpoint secret—use raw request body and correct secret key immediately.

Stripe: Webhook Signature Verification Failing [2am SOS]

TL;DR

Cause: Your webhook handler is verifying against a parsed/stringified body instead of the raw request stream, OR you're using the wrong endpoint secret. Fix: Pass the raw unparsed request body buffer to stripe.webhooks.constructEvent() and verify you're using your *endpoint's* secret (not your API key).

---

Real Console Error Messages

``` 1. Error: No signatures found matching the expected signature for payload.

2. StripeSignatureVerificationError: Unable to extract timestamp and signatures from header stripe-signature

3. Error: Webhook Error: invalid payload signature

4. TypeError: Cannot read property 'id' of undefined (downstream, triggered by failed verification)

5. 403 Forbidden - Stripe webhook signature verification failed ```

---

The Problem: Side-by-Side Code Comparison

❌ BROKEN (Most Common)

```javascript // Express.js with body-parser (DEFAULT SETUP) const express = require('express'); const bodyParser = require('body-parser'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express(); // THIS IS THE KILLER: body-parser JSON middleware parses the body app.use(bodyParser.json());

app.post('/webhook', (req, res) => { // req.body is NOW a parsed JavaScript object, NOT raw bytes // Stripe's signature is computed against the original raw bytes // These don't match → verification fails const event = stripe.webhooks.constructEvent( req.body, // 🚨 WRONG: This is parsed JSON, not raw body req.headers['stripe-signature'], process.env.STRIPE_ENDPOINT_SECRET ); res.sendStatus(200); }); ```

✅ FIXED (Correct Implementation)

```javascript // Express.js - CORRECT approach const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();

// CRITICAL: Express raw body middleware BEFORE other parsers app.use(express.raw({type: 'application/json'}));

// NOW apply JSON parsing to everything EXCEPT /webhook app.use((req, res, next) => { if (req.path === '/webhook') { return next(); } express.json()(req, res, next); });

app.post('/webhook', (req, res) => { // req.body is now a Buffer containing raw bytes // This matches Stripe's signature computation const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent( req.body, // ✅ CORRECT: Raw buffer sig, process.env.STRIPE_ENDPOINT_SECRET // ✅ Use ENDPOINT secret, not API key ); } catch (err) { console.error('Webhook Error:', err.message); return res.sendStatus(400); }

// Process event safely console.log(Received event: ${event.type}); res.sendStatus(200); }); ```

Alternative: Fastify Fix

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

const app = fastify();

// Fastify: Tell it NOT to parse webhook route app.register(require('@fastify/raw-body'), { routes: ['/webhook'] });

app.post('/webhook', async (request, reply) => { // request.rawBody contains the raw bytes const event = stripe.webhooks.constructEvent( request.rawBody, request.headers['stripe-signature'], process.env.STRIPE_ENDPOINT_SECRET ); reply.code(200).send({ ok: true }); }); ```

---

Critical Checklist

1. Environment Variable Verification: ```bash # In your .env or hosting platform: STRIPE_ENDPOINT_SECRET=whsec_test_4eC39HqLyjWDarhtT657j8saw # NOT your API key STRIPE_SECRET_KEY=sk_test_4eC39HqLyjWDarhtT657j8saw ``` Get the endpoint secret from [Stripe Dashboard → Webhooks → Your Endpoint → Signing Secret]

2. Confirm raw body is Buffer: ```javascript app.post('/webhook', (req, res) => { console.log('Body type:', typeof req.body, 'Is Buffer:', Buffer.isBuffer(req.body)); // Should output: "Body type: object Is Buffer: true" }); ```

3. Middleware Order Matters: - express.raw() MUST come before express.json() for this route

---

Still Broken? Check These Too

1. [Webhook Endpoint Not Registered Properly](/?guide=stripe-webhook-registration) — Verify endpoint exists in Dashboard and is enabled. Test button works but curl fails? Check firewall/network ACLs blocking Stripe IP ranges (35.184.0.0/13).

2. [Stripe.js vs @stripe/stripe-js Version Mismatch](/?guide=stripe-version-conflict) — Using stripe npm package v8 with Node 14? Explicit compatibility check required. Run npm ls stripe and cross-reference [official SDK changelog](https://github.com/stripe/stripe-node/releases).

3. Clock Skew Issues — Stripe rejects signatures older than 5 minutes. If your server time is off by >300s, verification fails silently. Check: date command on server vs NTP.

---

Official References

  • [Stripe Webhooks Documentation](https://stripe.com/docs/webhooks)
  • [Stripe Node.js SDK - constructEvent](https://github.com/stripe/stripe-node#webhooks)
  • [Express Raw Body Middleware](https://expressjs.com/en/api/express.html#express.raw)
  • ---

    Found a different variation? Drop it in the comments — Common setups: Next.js API routes, AWS Lambda, Google Cloud Functions, or custom request body buffering strategies. Your fix might save someone 2 hours of debugging at 2am.

    🔥 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