Clerk webhook verification failed? Here's the Svix signature, decoded

Your Next.js route calls verifyWebhook(req), Clerk's dashboard shows the delivery as failed (or your logs show Webhook verification failed), and the event never reaches your database sync. Clerk delivers webhooks through Svix, so "verification failed" almost always means one of five concrete things went wrong with the Svix signature check. Here's how the signature actually works, the five causes in rough order of likelihood, and how to isolate which one you have in about a minute.

How the Svix signature works

Every delivery carries three headers:

The signature is HMAC-SHA256 over the string {svix-id}.{svix-timestamp}.{raw body}, base64-encoded. The key is not the whsec_… string itself: it's the base64-decoded bytes of the part after whsec_. Verifiers must strip the v1, prefix, compare in constant time, and (in the official libraries) also enforce a timestamp tolerance to block replays. Some Svix plans white-label the headers to webhook-id / webhook-timestamp / webhook-signature — same scheme, and it's also exactly what the Standard Webhooks spec uses, which is why the identical check covers Resend, Railway, and a growing list of senders.

The five classic failures

Isolate it in one minute

The fastest way to tell "wrong secret" from "body mangled by my framework" is to take your framework out of the loop. Hookden gives you a capture URL that verifies Svix signatures for you:

1. Create a bin → https://hookden.pages.dev
2. Bin settings → Signature verification → "Svix / Standard Webhooks"
   → paste the endpoint's whsec_… secret
3. Add the /h/… URL as a webhook endpoint in the Clerk Dashboard
   and send a test event

Every capture gets a ✓ / ✗ badge:

The capture also shows the exact svix-id / svix-timestamp / svix-signature headers and raw body, so you can replay the same bytes against your local verifier until it passes.

Svix's retry schedule (why failures multiply)

When your endpoint fails, Svix retries with backoff: immediately, then 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours again. Retries of the same event reuse the same svix-id — handy for spotting duplicates, and it means one broken deploy turns into a day of red entries in the dashboard. Deliveries you missed entirely can be replayed from Clerk's dashboard once verification is fixed.

Local development

Clerk can't reach localhost:3000. Either run a tunnel, or skip the tunnel: point the Clerk endpoint at a bin and relay captures to localhost — the relay re-delivers the byte-identical body and the svix-* headers, so verifyWebhook() passes against the relayed request exactly as if Svix had called your machine directly.

Related: Stripe signature verification failed (hex HMAC over t.body — similar shape, different encoding) and webhooks to localhost without a tunnel.

No signup needed. Or from your terminal: curl https://hookden.pages.dev/new

← All guides · Docs · Hookden vs webhook.site