# Fix Stripe webhook signature verification failing

## TL;DR
Signature verification fails when your endpoint checks the signature against the wrong webhook secret, or you parse the raw body before verifying. Use the raw request body and the matching secret from the Stripe dashboard, and verify with Stripe's library. Never verify against a parsed JSON body.

## The error
```text
Stripe webhook handler error
No signatures found matching the expected signature for payload
```

## Use this skill
Agent? Search Vectle for this error:
```bash
curl -s "https://vectle.com/api/v1/search?q=webhook signature verification failed stripe"
```

## Fix it

### Step 1: Get the raw body to your verifier

```bash
Make sure your framework hands the verifier the raw bytes, not the parsed JSON. In Express that means express.raw for the webhook route.
```

Expected: The verifier receives bytes identical to what Stripe sent.

### Step 2: Confirm the webhook secret matches the endpoint

```bash
Stripe Dashboard -> Developers -> Webhooks -> [endpoint] -> copy the signing secret and compare with what your app uses.
```

Expected: The values match. Test-mode and live-mode secrets are different; check you are not mixing them.

### Step 3: Verify with the official library

```bash
Use the Stripe SDK's webhook verification helper with the raw body, the signature header, and the secret.
```

Expected: Verification passes for a real Stripe event.

### Step 4: Replay a test event

```bash
From the dashboard, send a test webhook to the endpoint.
```

Expected: Your handler returns 200 and processes the event.

### Step 5: Log verification failures with the event id

```bash
On failure, log the event id and timestamp so you can reconcile from the dashboard.
```

Expected: Missed events are visible and replayable from Stripe.

## When this applies

- Stripe webhooks return 400 on signature verification
- Webhooks work in test mode but fail in live mode
- You just changed frameworks or added body parsing middleware

## When it doesn't

- The endpoint never receives events (check the URL and firewall)
- Verification passes but handling fails (check your event logic)
- You use a different provider's webhooks (each signs differently)

## Compatibility

Stripe API webhooks. Official Stripe SDKs (Node, Python, Ruby, and others).

## Variant phrasings

### stripe no signatures found matching expected signature

The classic message. It means the secret or the body bytes are wrong, not that Stripe is broken.

### stripe webhook 400 invalid signature

Same check. Frameworks that parse JSON before the route runs are the top cause.

### stripe webhook secret mismatch live mode

Test and live secrets differ. Using the test secret against live events fails every time.

## Why it happens

Stripe signs the exact raw bytes it sends. Any transformation, JSON parsing and re-serializing, middleware altering the body, or the wrong secret, changes what you verify against and the signature check fails. The check is doing its job; your inputs to it are wrong.

## Edge cases

- Clock skew beyond the tolerance window fails verification; keep server time synced
- Multiple webhook endpoints each have their own secret; match secret to endpoint
- Return 200 quickly and process async; slow handlers get retried and double-processed

## If it still fails

- Reproduce with a test event from the provider dashboard to separate delivery problems from handler bugs.
- Log the raw payload shape, never customer PII, so the next failure is comparable.
- Check the provider status page; delivery outages mimic endpoint bugs.
- Replay a known-good event after the fix to prove the path works, not just that errors stopped.
- If signature failures persist with correct code, rotate the signing secret once; stale secrets cause silent mismatches.

## Prevention

- Return 200 fast and process async on every new webhook receiver.
- Make event handling idempotent from day one; retries are guaranteed.
- Monitor delivery success rates, not just endpoint uptime.
- Keep signing secrets per endpoint and rotate them on a schedule.
- Reconcile critical events against the provider API, not just webhooks.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_sytVRJHTB1HwC6vsQjbHLg
