Skip to content
Error

Webhook signature mismatch: why verification fails

Signature checks fail for a short list of reasons — almost always the body was mutated before signing, or the wrong secret or encoding was used.

A signature mismatch means the HMAC your endpoint computed does not equal the one in the request header, so you reject the delivery with 401 or 400. The signature is correct far more often than not — the mismatch is usually about what you signed.

Common causes

  • Parsed vs raw body — a framework parsed the JSON before you signed it, so you hashed a re-serialized string with different whitespace/key order. Sign the raw bytes.
  • Wrong secret — a test-mode secret against live traffic, or an endpoint-specific secret mixed up.
  • Encoding — comparing hex to base64, or different casing.
  • Timestamp tolerance — providers like Stripe sign timestamp + "." + body and reject stale timestamps; a retried or delayed delivery can fall outside the window.

Test your signature right now

Paste your raw payload and signing secret below to calculate the exact HMAC hex and base64 digest in your browser:

Payload
Signing secret
Algorithm
Expected signature (optional)

Everything runs in your browser via the Web Crypto API. Your secret and payload are never sent anywhere.

The fix: get the raw request body

Most web frameworks parse incoming JSON into an object automatically. Re-serializing with JSON.stringify() produces different key orders and spacing, which invalidates the HMAC hash. Verify against unparsed raw bytes instead:

Express / Node.js
// Preserve raw buffer for webhook routes
app.use('/webhooks', express.raw({ type: 'application/json' }));
Next.js App Router (route.ts)
// Read raw text before any JSON parsing
export async function POST(req: Request) {
  const rawBody = await req.text();
  // verify signature with rawBody...
}
Python (FastAPI)
# Read raw request body bytes
@app.post("/webhooks")
async def handle_webhook(request: Request):
    raw_body = await request.body()
    # compute HMAC-SHA256 over raw_body...

Confirm it in HookWatch

For Stripe specifically, see our guide on Stripe signature verification. In HookWatch, every captured delivery records the exact byte-for-byte raw body and all provider headers, so you can inspect the failure reason and replay it in one click once fixed.

Get started

Start debugging your webhooks.

Point one endpoint at HookWatch, capture a failure, and replay it once it’s fixed. Free during beta.