48-hour launch: 40% off with code LAUNCH40, Guide $5.40 / Kit $11.40

Stripe: “Unable to extract timestamp and signatures from header” — how to fix

This error is about the second argument you pass to constructEvent / construct_event, not about your body or your secret. Stripe's library splits that value on commas and looks for t=<timestamp> and v1=<signature>. A real Stripe-Signature header looks like this:

t=1728480000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39

If there's no parseable t= in the value (or the value isn't a string at all), you get this error. If there's a t= but no v1=, you get No signatures found with expected scheme instead.

Causes, most common first

  1. Arguments in the wrong order. The signature is constructEvent(rawBody, signatureHeader, endpointSecret). Passing the whsec_… secret second, and the header third, produces exactly this error.
  2. You passed the whole headers object (req.headers, request.headers) instead of the one header's value.
  3. You read a different header. For example Authorization, a header your own proxy adds, or a value you made up when testing with curl or Postman.
  4. The header is missing, on an older library. stripe-node up to v11.0 and older stripe-python releases (v7, for example) report a missing header with this same message. Newer versions say No stripe-signature header value was provided; see that page for the missing-header causes (header-name casing, API gateways, proxies).
  5. The test request wasn't from Stripe. Hand-made requests don't have a real signature. Use stripe listen --forward-to localhost:3000/webhook plus stripe trigger payment_intent.succeeded, or Resend an event from the Dashboard.

Check it in one line

Log the type and the start of the value you pass. The signature header isn't secret, but never log your whsec_ secret.

console.log(typeof sig, String(sig).slice(0, 20));   // expect: string t=1728480000,v1=...

Express

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];        // one header's value; Node lower-cases header names
  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);   // body, header, secret
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  res.sendStatus(200);
});

Next.js (App Router)

export async function POST(req) {
  const body = await req.text();
  const sig = req.headers.get('stripe-signature');    // a string, not the Headers object
  try {
    const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    return new Response(`Webhook Error: ${err.message}`, { status: 400 });
  }
  return new Response(null, { status: 200 });
}

Python (FastAPI, Django, Flask)

# FastAPI:  sig = request.headers.get("stripe-signature")
# Django:   sig = request.headers.get("Stripe-Signature")   # or request.META.get("HTTP_STRIPE_SIGNATURE")
# Flask:    sig = request.headers.get("Stripe-Signature")
event = stripe.Webhook.construct_event(payload, sig, endpoint_secret)   # payload, header, secret

Full Python handlers are on stripe.SignatureVerificationError in Python.

Once the header parses, the next error you may see is “No signatures found matching the expected signature for payload”. That one is about the raw body or the secret.

Header fixed, body still failing? The guide covers every common Stripe webhook failure with copy-paste fixes. The kit is a self-hosted Cloudflare Worker relay that verifies Stripe signatures, then retries, replays or dead-letters each event instead of dropping it.

Fix Your Stripe Webhooks guide · Self-Hosted Webhook Relay Kit. Code LAUNCH40 = 40% off until Sun Oct 11, 6:40 AM MT.

Free download: Webhook debugging cheat sheet.

Still failing? Paste your payload, header and secret into the free Stripe signature checker. It runs in your browser and tells you which cause it is.

All five common causes with copy-paste fixes: Fix Your Stripe Webhooks guide ($9). Retries, replay and a dead-letter list in front of your endpoint: Self-Hosted Webhook Relay Kit ($19).