HookShield
Start monitoring

STRIPE WEBHOOKS

HTTP 400

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. 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. 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. 3

    Exclude the route from middleware

    Bypass auth, CSRF, and body-parsing middleware for /webhooks/stripe specifically.

  4. 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.

More Stripe webhook fixes