Stripe Webhook Signature Verification Failed? No signatures found?

Complete guide to webhooks, debugging, and real-time event monitoring with HookMetry

Stripe Webhook Signature Verification Failed? No signatures found?

Problem Introduction

Stripe webhooks hit your endpoint but validation throws "No signatures found matching the expected signature for payload".

Why It Happens

  • Body-parser consumed the raw stream before verification
  • Using API Key (sk_live_) instead of Webhook Secret (whsec_)
  • Accidental quotes around the ENV variable value
  • Timestamp replay exceeding Stripe's 5-minute tolerance

Step-by-Step Fix

  1. 1Pass the exact raw Buffer/String to stripe.webhooks.constructEvent() — never parsed JSON.
  2. 2Confirm your secret starts with "whsec_" — found at Dashboard → Webhooks → Signing secret.
  3. 3Remove extra quotes from .env: STRIPE_WEBHOOK_SECRET=whsec_xxx (no surrounding quotes).
  4. 4For expired timestamps (>5 min old), use Stripe CLI for local testing instead of Dashboard replays.
  5. 5Never call JSON.stringify(req.body) to reconstruct the payload — it reorders keys.

Working Code

Copy-paste verified examples. Use the tab that matches your stack.

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

// express.raw() must run BEFORE express.json() — apply only to webhook route
app.post(
  '/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,                              // must be raw Buffer
        sig,
        process.env.STRIPE_WEBHOOK_SECRET      // whsec_...
      );
    } catch (err) {
      // "No signatures found"  → raw body was parsed (use express.raw)
      // "Timestamp outside tolerance" → replay expired; use Stripe CLI
      console.error('Stripe error:', err.message);
      return res.status(400).send('Webhook Error: ' + err.message);
    }

    switch (event.type) {
      case 'payment_intent.succeeded':
        await fulfillOrder(event.data.object);
        break;
      case 'customer.subscription.deleted':
        await cancelSubscription(event.data.object);
        break;
    }

    res.json({ received: true });
  }
);

Common Mistakes

  • Using express.json() before express.raw() on the webhook route
  • Using sk_test_ or sk_live_ key instead of whsec_ secret
  • Calling JSON.stringify on req.body to rebuild the string

Debugging Workflow

Receive raw bytes → pass to Stripe SDK → handle event type → return 200.

Preventive Best Practices

  • Use Hookmetry to instantly diagnose structural anomalies in Stripe payloads

Works with webhooks and other async event systems (including AI callbacks).

Instead of guessing, inspecting the exact payload and headers can help debug faster. Tools like Hookmetry support this workflow.

Try the free webhook tester

Was this page helpful?

Your feedback helps us improve the docs.