Stripe Webhooks Constructevent Not Working
Quick answer: keep the body raw, read the `Stripe-Signature` header directly from the request, and use the signing secret for that exact webhook endpoint. Per Stripe docs, Sept 2026, Dashboard endpoint secrets and `stripe listen` secrets both start with `whsec_`, but they are different secrets and cannot be swapped.
Why This Happens
- The request body was parsed, reordered, re-encoded, or otherwise changed before Stripe verification.
- The code is using the wrong `whsec_` secret for the endpoint or environment.
- The handler is reading the wrong signature header or never receiving it from the framework.
What To Check First
- Log whether your handler receives a raw string or buffer before `constructEvent()`.
- Compare the configured secret to the endpoint shown in Stripe Workbench or to the CLI output.
- Make sure JSON body parsing runs after the webhook route, not before it.
When To Escalate
- The problem is costing you money or customers per week
- You've spent more than 2 hours on it without progress
- A vendor quoted you more than $500 and you're not sure if it's necessary
FAQ
- Why does Stripe constructEvent fail? Per Stripe webhook signature docs, Sept 2026, `constructEvent()` needs the exact raw request body, `Stripe-Signature` header, and endpoint signing secret. If any one is wrong, signature verification fails.
- Which Stripe webhook secret should I use? Use the secret for the exact endpoint sending the event. Per Stripe docs, Sept 2026, CLI-forwarded events use the secret printed by `stripe listen`, not the Dashboard endpoint secret.
- Can JSON parsing break Stripe signature checks? Yes. Per Stripe webhook docs, Sept 2026, frameworks that parse or mutate the request body before verification can cause signature checks to fail.
Dealing with this right now?
Debugging checklist: (1) Log `typeof body` in your handler — it should be a `Buffer` or raw string, not a parsed object. If it is an object, your framework's body parser ran first. (2) Compare the first 8 characters of `process.env.STRIPE_WEBHOOK_SECRET` to what you see in Dashboard → Developers → Webhooks → Signing secret. (3) Check your server's system clock — Stripe rejects events where the timestamp header differs from your server time by more than 300 seconds. Run `date -u` on your server to verify.