Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch usually means misaligned endpoint secret or raw body modification. Verify you're using the correct signing secret and passing the raw request body untouched.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: Your endpoint is using the wrong webhook signing secret or receiving a parsed/modified request body instead of raw bytes. Fix: Match your endpoint secret to Stripe Dashboard, ensure middleware doesn't parse the body before signature verification, and always use req.rawBody or equivalent raw stream.

---

Real Console Error Messages

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

``` StripeSignatureVerificationError: Unable to extract timestamp and signatures from header 'stripe-signature' ```

``` Webhook signature verification failed: Computed signature does not match stripe-signature header ```

``` Error: Webhook endpoint returned 500 Internal Server Error - Webhook endpoint failed. Reason: Webhook signature verification failed ```

``` StripeSignatureVerificationError: Invalid signature: timestamp outside the tolerance window ```

---

The Problem: Code Comparison

❌ BROKEN CODE

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

app.use(express.json()); // ⚠️ PROBLEM: parses body BEFORE webhook handler

app.post('/webhook', (req, res) => { const sig = req.headers['stripe-signature']; const secret = process.env.STRIPE_API_KEY; // ⚠️ WRONG: using secret key instead of webhook secret try { const event = stripe.webhooks.constructEvent(req.body, sig, secret); // req.body is already parsed - signature verification fails res.json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ CORRECT CODE

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

// ✅ FIX: Keep webhook route BEFORE express.json() middleware app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const secret = process.env.STRIPE_WEBHOOK_SIGNING_SECRET; // ✅ Use webhook signing secret try { // ✅ req.body is raw Buffer, signature verification works const event = stripe.webhooks.constructEvent(req.body, sig, secret); switch(event.type) { case 'payment_intent.succeeded': console.log('Payment succeeded:', event.data.object.id); break; case 'charge.failed': console.log('Charge failed:', event.data.object.id); break; } res.json({received: true}); } catch (err) { console.error('Webhook error:', err.message); res.status(400).send(Webhook Error: ${err.message}); } });

// ✅ FIX: Add other routes AFTER webhook endpoint app.use(express.json()); app.post('/api/other-route', (req, res) => { // This route gets parsed JSON safely }); ```

Alternative Fix for Next.js API Routes

```javascript // pages/api/webhooks/stripe.js - CORRECT import { buffer } from 'micro'; import stripe from 'stripe';

const stripeClient = stripe(process.env.STRIPE_SECRET_KEY); const webhookSecret = process.env.STRIPE_WEBHOOK_SIGNING_SECRET;

export const config = { api: { bodyParser: false, // ✅ Disable automatic body parsing }, };

export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).end(); }

try { const buf = await buffer(req); // ✅ Get raw buffer const sig = req.headers['stripe-signature']; const event = stripeClient.webhooks.constructEvent( buf, sig, webhookSecret );

// Handle event console.log(Event type: ${event.type}); return res.json({received: true}); } catch (err) { return res.status(400).send(Webhook error: ${err.message}); } } ```

---

Critical Setup Checklist

1. Webhook Signing Secret: Go to [Stripe Dashboard](https://dashboard.stripe.com/webhooks) → Click your endpoint → Copy signing secret (starts with whsec_) ✅ Verify it matches process.env.STRIPE_WEBHOOK_SIGNING_SECRET exactly

2. Middleware Order: ✅ Webhook route MUST come BEFORE express.json() middleware

3. Raw Body Requirement: ✅ Signature verification needs the exact bytes Stripe sent—no parsing allowed

4. Timestamp Tolerance: ✅ Default window is 300 seconds; if your server time is severely out of sync, sync system clock

---

Still Broken? Check These Too

1. Using STRIPE_API_KEY instead of webhook secret: The signing secret is different from your API key. They both exist in Stripe Dashboard but serve different purposes. [View webhook authentication guide](/?guide=stripe-auth)

2. Body already parsed by middleware: If you're using body-parser, morgan with logging, or other middleware before the webhook route, the raw body is lost. Move the webhook handler before middleware chains.

3. Local testing with old timestamp: Stripe rejects webhooks older than 5 minutes. When replaying test events or testing with curl, ensure your system clock is synchronized. Use date command to verify. [Learn about webhook testing locally](/?guide=stripe-local-testing)

---

Version Note

This guide covers Stripe Node.js SDK v13+. If you're on v12 or earlier, stripe.webhooks.constructEvent() behavior differs slightly—verify your specific version's [official documentation](https://stripe.com/docs/webhooks/signatures#verify-manually).

---

Found a different variation? Drop it in the comments

Stripe webhook failures vary by framework, hosting environment, and middleware configuration. If you've hit a unique version—particularly with Python, PHP, or Go implementations—please share your error and fix below so others find it faster.

🔥 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