Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch: endpoint secret misconfigured or request body modified before verification. Use raw body buffer, never parsed JSON.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: Your endpoint is verifying the signature against parsed JSON instead of the raw request body, or your webhook endpoint secret is mismatched between Stripe dashboard and your code.

Fix: Pass the raw unparsed request body to stripe.webhooks.constructEvent() and verify your endpoint secret matches exactly in your .env file.

---

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 signing secret: whsec_... ```

``` Error: Unable to extract timestamp and signatures from header ```

``` SignatureVerificationError: No signatures found matching the expected signature for payload. [402] Request body does not match signature ```

``` Error: Invalid request: missing or invalid stripe-signature header ```

---

Broken Code vs. Exact Fix

❌ BROKEN (Express.js)

```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; try { // WRONG: Body is already parsed JSON const event = stripe.webhooks.constructEvent( req.body, // ← PROBLEM: Should be raw buffer sig, endpointSecret ); res.json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED (Express.js)

```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

// Use raw body parser ONLY for webhook endpoint app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; // Verify endpointSecret exists if (!endpointSecret) { return res.status(400).send('Webhook secret not configured'); } try { // CORRECT: Pass raw buffer const event = stripe.webhooks.constructEvent( req.body, // ← Raw Buffer object sig, endpointSecret ); // Handle specific events 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 signature verification failed:', err.message); res.status(400).send(Webhook Error: ${err.message}); } }); ```

Common Framework-Specific Setups

Next.js API Routes: ```javascript import { buffer } from 'micro';

export const config = { api: { bodyParser: false } };

export default async (req, res) => { if (req.method === 'POST') { const buf = await buffer(req); const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( buf, sig, process.env.STRIPE_WEBHOOK_SECRET ); // Process event... } }; ```

Django: ```python from django.views.decorators.csrf import csrf_exempt from django.http import JsonResponse import stripe

@csrf_exempt def stripe_webhook(request): payload = request.body # Raw bytes sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') endpoint_secret = os.environ['STRIPE_WEBHOOK_SECRET'] try: event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except ValueError: return JsonResponse({'error': 'Invalid payload'}, status=400) ```

---

Verification Checklist

1. Endpoint Secret Match - Go to Stripe Dashboard → Developers → Webhooks - Copy exact whsec_... value - Paste into your .env file: STRIPE_WEBHOOK_SECRET=whsec_live_... - Restart your server after .env change

2. Raw Body Requirement - Signature verification depends on byte-for-byte matching - Any JSON parsing before verification breaks the signature - Use express.raw() or framework equivalent

3. Middleware Order - Webhook route must come BEFORE global express.json() middleware ```javascript app.post('/webhook', express.raw({type: 'application/json'}), handler); app.use(express.json()); // Other routes ```

---

Still Broken? Check These Too

1. [Stripe API Key Format](/guide=stripe-key-validation) — Verify you're using sk_live_ (production) or sk_test_ (test mode), not publishable key

2. Webhook Endpoint Not Registered — In Stripe Dashboard, confirm webhook URL is exactly https://yourdomain.com/webhook (no trailing slash difference)

3. [Request Body Logging Issues](/guide=stripe-webhook-debugging) — Never log req.body after parsing; log the raw signature header instead

4. Timestamp Tolerance — Stripe webhooks fail if server time is >5 minutes out of sync; check system clock

5. Load Balancer/Proxy Modifying Body — Some proxies recompress or modify the request body; verify raw bytes match what Stripe sent

---

Official Documentation

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