# 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
```ts
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.