STRIPE WEBHOOKS
Stripe signature verification failed — the silent 400
A 400 on signature verification means Stripe is still "successfully" delivering from its perspective — it received a response — but your handler is rejecting every event. The webhook pipeline looks alive while zero events are processed.
Why Stripe webhooks fail silently
Wrong signing secret
whsec_ secrets differ between test and live mode, and per-endpoint. Rotating a secret or copying the wrong one silently invalidates every delivery.
Body mutated before verification
Signature is computed over the raw payload. If your framework parses JSON, trims whitespace, or reorders keys before verification (e.g. express.json() applied globally), verification fails on every request.
Middleware intercepts the route
Auth middleware, CSRF protection, or body size limits can reject Stripe’s POST before your handler runs — returning 401/413 instead of processing the event.
The fix
- 1
Verify against the raw body
Pass the unparsed request body to constructEvent. In Express, mount express.raw({type: "application/json"}) on the webhook route only — never the global JSON parser.
- 2
Check the correct whsec_ secret
Each endpoint has its own signing secret, and test/live modes differ. Pull it from Developers → Webhooks → your endpoint → "Signing secret".
- 3
Exclude the route from middleware
Bypass auth, CSRF, and body-parsing middleware for /webhooks/stripe specifically.
- 4
Monitor the 400s
With HookShield in front, every rejected delivery is logged with its status — you see the 400 wall forming instead of discovering it in a support ticket.
DEAD-MAN'S SWITCH
Never find out about a dead webhook from a customer again
Point Stripe at a HookShield ingest URL. Every delivery is logged, signed, and forwarded — and if pings stop arriving, you're alerted before the silence becomes an incident.