Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch from endpoint secret misconfiguration or timestamp validation. Use correct signing secret and verify within 5-minute window.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: Your endpoint is using the wrong signing secret (often the API key instead of the webhook endpoint secret) or the request timestamp is outside the 5-minute verification window.

Fix: Replace your secret with the correct whsec_* or we_* endpoint secret from Stripe Dashboard → Developers → Webhooks, and ensure server time is synchronized.

---

Exact Error Messages You'll See

These are real console outputs from webhook verification failures:

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

``` Stripe.error.SignatureVerificationError: Timestamp outside the tolerance window ```

``` Webhook signature verification failed: Invalid signature ```

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

``` Fatal error in Stripe webhook handler: Signature verification failed for event evt_* ```

---

The Problem: Broken vs. Fixed Code

❌ BROKEN CODE (Common Mistakes)

```javascript // Mistake 1: Using API key instead of webhook secret const stripe = require('stripe')(process.env.STRIPE_API_KEY); const secret = process.env.STRIPE_API_KEY; // WRONG!

app.post('/webhook', (req, res) => { try { const event = stripe.webhooks.constructEvent( req.rawBody, req.headers['stripe-signature'], secret // This will ALWAYS fail ); } catch (err) { return res.status(400).send(Webhook Error: ${err.message}); } }); ```

```javascript // Mistake 2: Not using raw body buffer app.use(express.json()); // Parsed JSON breaks signature

app.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent( req.body, // Already parsed - signature won't match! req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); }); ```

✅ FIXED CODE

```javascript // Correct implementation const stripe = require('stripe')(process.env.STRIPE_API_KEY); const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; // whsec_live_* or whsec_test_*

// CRITICAL: Handle webhook endpoint BEFORE express.json() app.post( '/webhook', express.raw({type: 'application/json'}), // Raw buffer, not parsed (req, res) => { const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent( req.body, // Raw buffer from express.raw() sig, webhookSecret // MUST be whsec_* endpoint secret ); } catch (err) { console.error(Webhook signature verification failed: ${err.message}); return res.status(400).send(Webhook Error: ${err.message}); } // Handle event switch (event.type) { case 'payment_intent.succeeded': // Process payment break; case 'charge.failed': // Handle failure break; } res.json({received: true}); } );

// Other routes CAN use express.json() app.use(express.json()); app.post('/api/other', (req, res) => { // This is fine }); ```

Key differences: 1. express.raw({type: 'application/json'}) middleware preserves the raw request body 2. webhookSecret starts with whsec_ (not your API key) 3. Webhook endpoint is defined BEFORE express.json()

---

Verification Checklist

1. Get the right secret: - Go to [Stripe Dashboard](https://dashboard.stripe.com/webhooks) → Developers → Webhooks - Find your endpoint URL - Click "Reveal" next to "Signing secret" - Copy the full whsec_* value

2. Verify environment variable: ```bash echo $STRIPE_WEBHOOK_SECRET # Should output: whsec_live_ABC123... or whsec_test_ABC123... # NOT your API key (sk_live_* or sk_test_*) ```

3. Check server time synchronization: ```javascript // Stripe rejects signatures older than 5 minutes console.log(new Date().toISOString()); // Should be within ±5 minutes of actual time ```

4. Test with Stripe CLI locally: ```bash stripe listen --forward-to localhost:3000/webhook stripe trigger payment_intent.succeeded ```

---

Still broken? Check these too

1. Multiple webhooks registered: You might be testing against the wrong endpoint. Verify in Dashboard that the URL exactly matches your live server (no staging/dev URLs mixed in).

2. Raw body middleware conflict: If using third-party middleware that modifies the request body before your webhook handler, it will break the signature. Load webhook route before any body-parsing middleware [related](/?guide=express-middleware-order).

3. Stripe library version: We're certain about behavior in stripe@14.0.0+ (2024+). If you're on stripe@8.x or earlier, timestamp tolerance may differ—check official docs for your version [related](/?guide=stripe-version-compatibility).

---

References

  • Official Stripe Webhook Docs: https://stripe.com/docs/webhooks/signatures
  • Node.js SDK Reference: https://github.com/stripe/stripe-node
  • Found a different variation? Drop it in the comments—we update this guide based on real production issues.

    🔥 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