Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch caused by endpoint secret misconfiguration or timestamp skew; verify your signing secret and clock sync.

Stripe: Webhook Signature Verification Failing [2am Emergency Fix]

TL;DR

Cause: Your endpoint is using the wrong webhook signing secret or your server's clock is skewed by >5 minutes. Fix: Verify STRIPE_WEBHOOK_SECRET env var matches your dashboard webhook endpoint, and sync your server time with NTP.

---

Real Console Error Messages

Here are the exact errors you'll see:

``` Error: No signatures found matching the expected signature for payload. Attempted to construct signature using the following secret: 'whsec_live_***' but no signatures found in the 'stripe-signature' header. ```

``` StripeSignatureVerificationError: No valid signature found. ```

``` Webhook error: Unable to extract timestamp and signatures from header. Header value: undefined ```

``` Error: Timestamp outside the tolerance window: received 1704067200, current 1704067920 (timestamp difference: 720 seconds exceeds max tolerance of 300 seconds) ```

``` Signature verification failed. Expected sig: sha256=abc123..., got: sha256=xyz789... ```

---

The Problem: Side-by-Side Code Fix

❌ BROKEN CODE

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

app.post('/webhook', express.json(), (req, res) => { // WRONG: Using parsed JSON body directly const event = req.body; // WRONG: Not verifying signature at all if (event.type === 'payment_intent.succeeded') { console.log('Payment successful:', event.data.object.id); res.json({received: true}); } }); ```

✅ CORRECT CODE

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

// CRITICAL: Must be raw body, NOT parsed JSON app.post('/webhook', express.raw({type: 'application/json'}), // Raw buffer required (req, res) => { const sig = req.headers['stripe-signature']; const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; // whsec_live_*** let event; try { // Signature verification happens HERE event = stripe.webhooks.constructEvent( req.body, // Raw body buffer sig, // Signature from header webhookSecret // Your endpoint's signing secret ); } catch (err) { console.error('Webhook signature verification failed:', err.message); return res.status(400).send(Webhook Error: ${err.message}); } // Only process AFTER verification passes if (event.type === 'payment_intent.succeeded') { console.log('Payment successful:', event.data.object.id); } res.json({received: true}); } ); ```

Key differences:

  • express.raw() instead of express.json() — the signature is computed on the raw bytes
  • stripe.webhooks.constructEvent() verifies the signature before parsing
  • Explicit error handling for verification failures
  • ---

    Verification Checklist (Do This Now)

    1. Verify Your Webhook Secret

    Go to [Stripe Dashboard → Developers → Webhooks](https://dashboard.stripe.com/webhooks). Find your endpoint:

    ```bash

    Should match exactly

    echo $STRIPE_WEBHOOK_SECRET

    Should output: whsec_live_4eC39HqLyjWDarhtT1g3bZ8y or whsec_test_...

    ```

    If it says pk_live_... or sk_live_..., you copied the wrong key. Copy the Signing secret under "Endpoint details."

    2. Check Server Clock Skew

    Stripe rejects signatures older than 5 minutes. Run:

    ```bash

    Linux/Mac

    date && curl -s https://worldtimeapi.org/api/timezone/Etc/UTC | grep unixtime

    If difference > 300 seconds, sync your clock

    sudo ntpdate -s time.nist.gov # or use timedatectl on systemd ```

    3. Validate Raw Body Handling

    Add this debug endpoint temporarily:

    ```javascript app.post('/webhook-debug', express.raw({type: 'application/json'}), (req, res) => { console.log('Body type:', typeof req.body); console.log('Body is Buffer:', Buffer.isBuffer(req.body)); console.log('Signature header:', req.headers['stripe-signature']); res.json({ok: true}); }); ```

    Test with Stripe CLI:

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

    ---

    Still Broken? Check These Too

    1. Multiple middleware parsing JSON: If you have app.use(express.json()) globally, it parses the body before your raw middleware sees it. Move webhook routes BEFORE global JSON middleware, or skip JSON parsing for /webhook* routes.

    2. Ngrok/proxy stripping the signature header: Some reverse proxies drop custom headers. Verify headers are passing through: curl -v https://your-tunnel.ngrok.io/webhook and check response includes stripe-signature.

    3. Environment variable not loaded: In Node.js, process.env is read at startup. If you added STRIPE_WEBHOOK_SECRET to .env after starting the server, restart: node app.js (not just saving the file).

    ---

    Related Guides

  • [Stripe API Key Rotation: Avoid the downtime trap](/?guide=stripe-key-rotation)
  • [Express Middleware Order: Why Your Auth Middleware Isn't Running](/?guide=express-middleware-order)
  • ---

    Official Documentation

    [Stripe Webhook Signature Verification](https://stripe.com/docs/webhooks/signatures) [Stripe Node.js Library Reference](https://stripe.com/docs/api/libraries)

    ---

    Found a different variation? Drop it in the comments below.

    🔥 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