Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch due to raw body parsing or endpoint secret misconfiguration. Use raw body buffer and verify correct secret from Dashboard.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: You're parsing the request body as JSON before verification, or using the wrong endpoint secret. Fix: Pass the raw request body (as Buffer) to stripe.webhooks.constructEvent() and confirm your endpoint secret matches Dashboard → Developers → Webhooks.

---

Real Console Error Messages

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

``` WebhookSignatureVerificationError: No matching signature found for payload. Details: expected_sig=whsec_..., received_sig=undefined ```

``` StripeSignatureVerificationError: Webhook signature verification failed. Timestamp outside tolerance window. Tolerance: 300, Current: 450 seconds ago ```

``` Error: Webhook endpoint secret is invalid or not found in environment ```

``` TypeError: Cannot read property 'id' of undefined - webhook event failed to construct ```

---

Broken Code vs. Exact Fix

Problem #1: JSON Parsing Before Verification

BROKEN: ```javascript app.post('/webhook', (req, res) => { // ❌ Body already parsed to JSON - signature check will fail const body = JSON.parse(req.body); const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( body, // ❌ WRONG: Already stringified/parsed sig, process.env.STRIPE_WEBHOOK_SECRET ); }); ```

FIXED: ```javascript app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { // ✅ req.body is Buffer - kept raw for signature verification const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent( req.body, // ✅ CORRECT: Raw Buffer sig, process.env.STRIPE_WEBHOOK_SECRET ); // Now safe to use event console.log('Event type:', event.type); res.json({ received: true }); } catch (error) { console.error('Webhook signature verification failed:', error.message); res.status(400).send(Webhook Error: ${error.message}); } }); ```

Problem #2: Wrong Endpoint Secret

BROKEN: ```javascript const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_SECRET_KEY // ❌ WRONG: This is your API key, not webhook secret ); ```

FIXED: ```javascript const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET // ✅ CORRECT: whsec_live_... or whsec_test_... ); ```

Problem #3: Using Body Parser Middleware Globally

BROKEN: ```javascript app.use(express.json()); // ❌ Parses ALL bodies before route handlers

app.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent( req.body, // ❌ Already parsed - signature fails sig, process.env.STRIPE_WEBHOOK_SECRET ); }); ```

FIXED: ```javascript // Apply JSON parsing to all routes EXCEPT webhook app.use((req, res, next) => { if (req.path === '/webhook') { return next(); // Skip middleware for webhook } express.json()(req, res, next); });

// OR: Use raw body ONLY for this endpoint app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const event = stripe.webhooks.constructEvent( req.body, // ✅ Raw Buffer preserved sig, process.env.STRIPE_WEBHOOK_SECRET ); });

// Other routes get JSON parsing normally app.post('/api/charges', express.json(), (req, res) => { // req.body is parsed JSON here - safe }); ```

---

Verification Checklist

1. Get the correct webhook secret: - Go to [Stripe Dashboard](https://dashboard.stripe.com/developers/webhooks) - Find your endpoint URL - Copy the "Signing secret" (starts with whsec_) - Add to .env: STRIPE_WEBHOOK_SECRET=whsec_live_xxx

2. Confirm raw body is used: ```bash # In your logs, req.body should be a Buffer: console.log(typeof req.body, Buffer.isBuffer(req.body)); // Output: object true ✅ ```

3. Check environment variables are loaded: ```javascript console.log('Webhook secret:', process.env.STRIPE_WEBHOOK_SECRET?.substring(0, 10) + '...'); // Should print: whsec_live_... or whsec_test_... ```

---

Still Broken? Check These Too

1. [Stripe API Key Configuration Errors](/guide=stripe-api-keys) — Using test key in production or vice versa causes signature mismatches.

2. Request Body Size Limits — Nginx/reverse proxy truncating webhook payload. Set client_max_body_size 10m; in nginx config.

3. [Clock Skew / Timestamp Validation](/guide=webhook-timestamp-errors) — Server time drifted >5 minutes from actual time. Run ntpdate -s time.nist.gov on Linux or sync system clock.

4. Multiple Webhook Endpoints — Testing against wrong endpoint URL. Verify in Stripe Dashboard that your URL matches exactly (domain, path, protocol).

5. Old Stripe SDK Version — Versions <11.0 have different signature validation. Update: npm install stripe@latest.

---

Official Documentation

  • [Stripe Webhook Signature Verification](https://stripe.com/docs/webhooks/signatures)
  • [Constructing Events](https://stripe.com/docs/webhooks/test)
  • [Environment Setup Guide](https://stripe.com/docs/keys)
  • ---

    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