This source did not publish a separate summary. Review SKILL.md before using the skill.
SKILL.md
Clerk Webhooks & Events
Overview
Configure and handle Clerk webhooks for user lifecycle events and data synchronization. Clerk uses Svix for webhook delivery with HMAC-SHA256 signature verification. As of 2025, Clerk provides a built-in verifyWebhook() helper in @clerk/backend alongside the manual Svix approach.
Prerequisites
Clerk account with webhook endpoint configured in Dashboard
HTTPS endpoint (use ngrok for local dev)
CLERK_WEBHOOK_SECRET environment variable (starts with whsec_)
Instructions
Step 1: Install Dependencies
# Option A: Use @clerk/backend's built-in verifyWebhook() (recommended)
# Already included with @clerk/nextjs — no extra install needed
# Option B: Manual Svix verification
npm install svix
Session events:session.created, session.ended (optional, high volume)
Copy the Signing Secret (whsec_...) to your .env.local:
CLERK_WEBHOOK_SECRET=whsec_...
Step 6: Express.js Webhook Endpoint
import express from 'express'
import { Webhook } from 'svix'
const app = express()
// CRITICAL: Use express.raw(), NOT express.json() for webhook routes
app.post('/api/webhooks/clerk',
express.raw({ type: 'application/json' }),
(req, res) => {
const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!)
try {
const evt = wh.verify(req.body, {
'svix-id': req.headers['svix-id'] as string,
'svix-timestamp': req.headers['svix-timestamp'] as string,
'svix-signature': req.headers['svix-signature'] as string,
})
// Handle event...
res.status(200).json({ received: true })
} catch (err) {
console.error('Webhook verification failed:', err)
res.status(400).json({ error: 'Invalid signature' })
}
}
)
Local Development with ngrok
# Start ngrok tunnel for local webhook testing
ngrok http 3000
# Copy the https://xxx.ngrok-free.app URL
# Add it as webhook endpoint in Clerk Dashboard > Webhooks
# URL: https://xxx.ngrok-free.app/api/webhooks/clerk
Error Handling
Error
Cause
Solution
Invalid signature
Wrong CLERK_WEBHOOK_SECRET
Re-copy signing secret from Dashboard > Webhooks
Invalid signature
Body parsed with json() before verify
Use req.text() (Next.js) or express.raw() (Express)
Missing svix headers
Request not from Clerk/Svix
Verify endpoint URL; check sender
Duplicate processing
Clerk retried delivery
Implement idempotency with svix-id as unique key
Handler timeout
Slow DB operations
Offload heavy work to a background job queue
404 on webhook URL
Route not matching
Ensure /api/webhooks is in middleware's isPublicRoute
Enterprise Considerations
Treat CLERK_WEBHOOK_SECRET like a password -- rotate it if compromised (Dashboard > Webhooks > Signing Secret > Rotate)
Svix headers include svix-timestamp for replay attack protection (rejects events older than 5 minutes by default)
For high-volume apps, offload webhook processing to a queue (BullMQ, Inngest, Trigger.dev) and return 200 immediately
Monitor webhook delivery in Dashboard > Webhooks > Message Logs -- failed messages auto-retry with exponential backoff
Use verifyWebhook() from @clerk/backend/webhooks when possible -- it handles header extraction and secret key resolution automatically