Your webhook endpoint is receiving Stripe events but signature verification throws an error. This is almost always a raw body parsing issue — your framework is parsing the JSON before Stripe can verify the original signature.
Stripe's signature is computed against the raw bytes of the request body. If your framework (Express, Next.js, etc.) parses the JSON body first (converting it to a JavaScript object), the raw bytes are gone. You must pass the raw buffer, not the parsed object. In Express: use express.raw({type: 'application/json'}) for the webhook route. In Next.js App Router: read req.text() not req.json().
stripe.webhooks.constructEvent() takes your webhook signing secret — not your Stripe API key. The signing secret starts with 'whsec_' and is found in Dashboard > Developers > Webhooks > your endpoint > Signing secret. Using your API key (sk_live_ or sk_test_) here will always fail.
Stripe has separate signing secrets for test mode and live mode webhooks. If you're testing with test events, use the test endpoint's whsec_. If receiving live events, use the live endpoint's whsec_. Cross-mode secrets always fail verification.
Stripe includes a timestamp in the webhook payload and rejects verification if the timestamp is more than 5 minutes old (configurable). If your server clock is off or you're replaying old events for testing, verification fails. Use stripe.webhooks.constructEvent(payload, header, secret, {tolerance: 300}) or increase tolerance for testing.
Body-parsing middleware (body-parser, next.js default JSON parser) applied globally will consume the raw body before your webhook handler sees it. Ensure your webhook route explicitly bypasses the global JSON parser and reads the raw body directly.
Real operator. No ticket queue. San Diego-based. Most issues resolved in one thread.
Related problems in this cluster: