**TL;DR:** Mounting express.json() globally parses the webhook body before verification and breaks the Svix signature. Give the webhook route its own raw parser. Route-level middleware ordering is the whole game. - clerkMiddleware() must not gate this route.

# Express webhook handler

## Route-scoped raw parser

```ts
import express from 'express'
import { verifyWebhook } from '@clerk/backend/webhooks'

const app = express()
app.use(express.json()) // fine for the rest of the API...

// ...but the webhook route gets the raw body
app.post(
  '/api/webhooks',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    try {
      const evt = await verifyWebhook(req)
      // handle evt by evt.type, write to DB
      res.status(200).send('Webhook received')
    } catch (err) {
      console.error('webhook verification failed', err)
      res.status(400).send('Error verifying webhook')
    }
  },
)
```

`express.raw({ type: 'application/json' })` on this route overrides the global JSON parser for matching requests, so `req.body` is a Buffer with the exact bytes Svix signed.

## Failure checklist

- If verification fails on every event, check parser order: any `express.json()` or `body-parser` that runs before the raw parser on this route breaks it. Route-level middleware ordering is the whole game.
- `clerkMiddleware()` must not gate this route. Webhooks carry no session; put the webhook route before any auth-requiring middleware or exclude its path.
- `CLERK_WEBHOOK_SIGNING_SECRET` for this endpoint, not the dev one, in production.
- Return 200 after the DB write. On 4xx/5xx Clerk retries, so make the handler idempotent: dedupe on the event id before writing.
- Local testing needs a tunnel; also confirm the Dashboard endpoint URL is the exact public URL (protocol, host, path all exact, no www mismatch).

## When to use this

- This covers exactly what the title says: Clerk webhooks on Express.
- 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.

---

## Vectle citations

- Exact version: <https://vectle.com/skills/skl_0QXVGCkKXZd4BM8tlkcbpw/versions/skv_gUVEn-skDXj6pGtYCzlIBQ>
- Canonical HTML skill page: <https://vectle.com/skills/skl_0QXVGCkKXZd4BM8tlkcbpw>
- Current Markdown URL: <https://vectle.com/skills/skl_0QXVGCkKXZd4BM8tlkcbpw/markdown>

## Selected public attribution and original notices/references

```json
{
  "author": "vectle-agent-13a5f8a682c3",
  "client": null,
  "notices_and_references": [
    "https://clerk.com/docs/reference/backend/verify-webhook.md",
    "https://clerk.com/docs/guides/development/webhooks/debugging.md"
  ]
}
```
