Stripe: webhook signature verification failing [2026 fix]

Webhook signature verification fails when endpoint secret is missing, misconfigured, or raw body isn't passed to verification function.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: Your code is passing the parsed JSON body instead of the raw request body to constructEvent(), or your endpoint secret is undefined/wrong. Fix: Use req.rawBody (or equivalent raw buffer) and verify STRIPE_WEBHOOK_SECRET is set in environment variables.

---

Real Console Error Messages

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

``` Stripe.error.SignatureVerificationError: No signatures found matching the expected signature for payload. Webhook id: whsvc_1234567890 ```

``` No signatures found matching the expected signature for payload. Webhook signing secret does not match. ```

``` this.signature === undefined at Object.verifyHeaderAndExtractTimestamp ```

``` stripe.webhooks.constructEvent is not a function (when using incorrect import) ```

---

Broken Code vs. Fixed Code

❌ BROKEN: Passing Parsed Body

```javascript // Express endpoint app.post('/webhook', (req, res) => { const payload = req.body; // ← WRONG: Already parsed JSON const sig = req.headers['stripe-signature']; try { // This fails because Stripe signs the raw bytes, not parsed JSON const event = stripe.webhooks.constructEvent( payload, sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED: Using Raw Body

```javascript // Express with raw body middleware const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();

// CRITICAL: Raw body for Stripe webhooks ONLY app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const payload = req.body; // Now this IS the raw buffer const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent( payload, sig, process.env.STRIPE_WEBHOOK_SECRET ); // Process event safely console.log(✓ Event verified: ${event.type}); res.status(200).json({received: true}); } catch (err) { console.error('Webhook signature verification failed:', err.message); res.status(400).send(Webhook Error: ${err.message}); } });

// Regular JSON parsing for other endpoints app.use(express.json()); ```

---

Environment & Config Checklist

Verify your .env file contains:

```bash STRIPE_WEBHOOK_SECRET=whsvc_1234567890abcdefghijklmnop STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxx ```

Verify secret is loaded:

```javascript if (!process.env.STRIPE_WEBHOOK_SECRET) { throw new Error('STRIPE_WEBHOOK_SECRET not set in environment'); } ```

Verify you're using the correct secret (not your signing secret, not API key). Get it from [Stripe Dashboard → Developers → Webhooks → Endpoint Details](https://dashboard.stripe.com/webhooks).

---

Framework-Specific Solutions

Next.js API Routes

```javascript // pages/api/webhooks/stripe.js export const config = { api: { bodyParser: false, // Disable default parser }, };

export default async function handler(req, res) { const buf = await new Promise((resolve, reject) => { let data = ''; req.on('data', chunk => { data += chunk; }); req.on('end', () => resolve(Buffer.from(data))); req.on('error', reject); }); const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent(buf, sig, process.env.STRIPE_WEBHOOK_SECRET); res.status(200).json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } } ```

FastAPI/Python

```python from fastapi import Request import stripe

stripe.api_key = os.getenv('STRIPE_SECRET_KEY') endpoint_secret = os.getenv('STRIPE_WEBHOOK_SECRET')

@app.post('/webhook') async def webhook_received(request: Request): body = await request.body() # Raw bytes sig_header = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event(body, sig_header, endpoint_secret) return {'status': 'success'} except stripe.error.SignatureVerificationError: return {'error': 'Invalid signature'}, 403 ```

---

Still Broken? Check These Too

1. Timestamp Tolerance: Stripe rejects signatures older than 5 minutes. Verify your server clock is synced. Run ntpdate -s time.nist.gov on Linux or check System Preferences → Date & Time on macOS.

2. Multiple Webhook Endpoints: You might have registered the webhook URL twice with different secrets, or you're testing against staging while code points to production. Double-check stripe.webhooks.constructEvent() receives the endpoint secret, not your API key.

3. Middleware Order Matters: If using multiple middleware, ensure express.raw() is before express.json() on the webhook route. Global express.json() will parse the body before your webhook handler runs. See [Express middleware guide](/?guide=middleware-order).

---

Test Your Webhook Locally

```bash

Use Stripe CLI to forward webhooks

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

This bypasses signature verification issues during development.

---

Version Notes

  • Stripe.js v3-v14: API stable; constructEvent() signature unchanged.
  • Stripe.js v15+: No breaking changes to webhook verification (as of 2026).
  • If you're on an older version (v8 or earlier), I'd recommend upgrading—security patches matter.
  • ---

    Official Documentation

    [Stripe Webhook Signature Verification](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