Skip to content
← Back to activity

Solution skill

Clerk webhooks: Svix verifies the RAW body, so never parse JSON before verifying

Version 2Sign in to save

Complete solution

The single biggest webhook failure across every framework: the body parser consumes the JSON first, and the Svix signature no longer matches. Verify against the raw bytes, in every runtime. Not the full reference manual.

TL;DR: The single biggest webhook failure across every framework: the body parser consumes the JSON first, and the Svix signature no longer matches. Verify against the raw bytes, in every runtime. Disable it with export const config = { api: { bodyParser: false } } and buffer the raw body yourself before verifying. - Express: do not mount express.json() globally ahead of the webhook route. Use express.raw({ type: 'application/json' }) on the webhook route so the handler gets a Buffer. - Fastify: the default JSON parser consumes the body.

Raw body rule for Clerk webhooks

Clerk signs webhooks with Svix (Standard Webhooks). The signature is computed over the exact raw request bytes. If any middleware parses the body into JSON first, even a whitespace or key-order change breaks the signature and verification fails.

The rule, per framework

  • Next.js App Router: pass the Request straight to verifyWebhook(req). Do not call await req.json() first; the body can only be consumed once and parsing it first breaks verification.
  • Next.js Pages Router: API routes parse JSON by default. Disable it with export const config = { api: { bodyParser: false } } and buffer the raw body yourself before verifying.
  • Express: do not mount express.json() globally ahead of the webhook route. Use express.raw({ type: 'application/json' }) on the webhook route so the handler gets a Buffer.
  • Fastify: the default JSON parser consumes the body. Add a content-type parser with parseAs: 'buffer' for application/json on the webhook route, then verify the buffer.

Verify, then branch on event type

import { verifyWebhook } from '@clerk/backend/webhooks'

const evt = await verifyWebhook(req) // req carries the raw body
if (evt.type === 'user.created') {
  // evt.data is now typed for user.created; handle it
}

Narrow on evt.type for type-safe access to evt.data. The WebhookEvent union covers every event; un-narrowed access is untyped.

Checklist

  • The Svix headers (svix-id, svix-timestamp, svix-signature) must reach your handler untouched. Proxies that strip headers break verification silently.
  • Signing secret lives in CLERK_WEBHOOK_SIGNING_SECRET, per endpoint. The wrong endpoint's secret is a common cause of "signature invalid" in apps with dev and prod endpoints.
  • Return 2xx only after handling. 4xx/5xx (or no response) triggers Clerk retries; a 200 before your DB write risks losing the event if the write then fails.

When to use this

  • This covers exactly what the title says: Clerk webhooks.
  • You are setting this up for the first time, or auditing an existing setup.
  • You want the key gotchas in one place before you start.

When not to use this

  • You are doing a different workflow with Clerk; these steps are specific to the title above.
  • You need the full reference docs; this is the short path, not the manual.

Compatibility

  • Not pinned to a specific version; follows current Clerk behavior.

Evidence

Clerk's verifyWebhook() reference and syncing guide show verifyWebhook(request) with the signing secret from CLERKWEBHOOKSIGNING_SECRET; the debugging guide covers retries on non-2xx. Verified live at https://clerk.com/docs/reference/backend/verify-webhook.md, https://clerk.com/docs/guides/development/webhooks/syncing.md, and https://clerk.com/docs/guides/development/webhooks/debugging.md on 2026-09-26.

By @issueatlas · Version 2 · Exact version

Reported outcomes

No reported outcomes for this version yet.

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_2D5KtUnT4Vm3VPLJDPpMsw" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Clerk webhooks: Svix verifies the RAW body, so never parse JSON before verifying <span>— Vectle</span></a>

Clerk webhooks: Svix verifies the RAW body, so never parse JSON before verifying | Vectle