Clerk Webhook Integration
Clerk webhooks (called Svix webhooks) allow you to receive real-time notifications about events in your Clerk application, such as user sign-ups, profile updates, organization changes, and session events.
Quick Start
- Get your Unhook URL:
https://unhook.sh/wh_YOUR_ID
- Configure in Clerk Dashboard: dashboard.clerk.com/webhooks
- Start receiving events locally:
unhook listen
Configuration Steps
1. Access Clerk Webhook Settings
Navigate to the Clerk Dashboard:
2. Create a New Endpoint
- Click “Add Endpoint”
- Enter your Unhook URL:
https://unhook.sh/wh_YOUR_ID
- Give your endpoint a descriptive name (e.g., “Development Webhooks”)
3. Select Events to Listen For
Choose the events relevant to your application:
User Events
user.created
- New user sign-up
user.updated
- User profile updated
user.deleted
- User account deleted
email.created
- Email address added
phoneNumber.created
- Phone number added
externalAccount.created
- Social login connected
externalAccount.deleted
- Social login disconnected
Session Events
session.created
- User signed in
session.ended
- User signed out
session.removed
- Session revoked
session.revoked
- Session invalidated
Organization Events
organization.created
- New organization created
organization.updated
- Organization settings changed
organization.deleted
- Organization removed
organizationMembership.created
- Member added
organizationMembership.updated
- Member role changed
organizationMembership.deleted
- Member removed
organizationInvitation.created
- Invitation sent
organizationInvitation.accepted
- Invitation accepted
organizationInvitation.revoked
- Invitation cancelled
Multi-factor Authentication Events
user.twoFactorEnabled
- 2FA enabled
user.twoFactorDisabled
- 2FA disabled
smsVerification.created
- SMS verification sent
emailVerification.created
- Email verification sent
After selecting events:
- Enable the endpoint with the toggle switch
- Copy the Signing Secret - you’ll need this to verify webhooks
- Click “Create” to save
Webhook Verification
Clerk uses Svix to sign webhooks. To verify signatures:
- After creating the endpoint, copy the Signing Secret
- It will look like:
whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
- Add it to your environment variables:
CLERK_WEBHOOK_SECRET=whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
Testing Webhooks
Using Clerk Dashboard
- Go to your webhook endpoint in the Dashboard
- Click on the endpoint
- Navigate to the “Testing” tab
- Select an event type
- Click “Send Example”
Using Clerk CLI
# Install Clerk CLI
npm install -g @clerk/clerk-cli
# Trigger test events (requires API key)
clerk webhooks test user.created
clerk webhooks test session.created
Event Payload Examples
User Created
{
"data": {
"id": "user_2NNEqL2nrIRxqh5TyNMcL3YXvVp",
"object": "user",
"created_at": 1654012345678,
"updated_at": 1654012345678,
"email_addresses": [
{
"id": "idn_2NNEqL2nrIRxqh5TyNOQW8s5VX5",
"email_address": "user@example.com",
"verification": {
"status": "verified",
"strategy": "email_code"
}
}
],
"first_name": "John",
"last_name": "Doe",
"username": "johndoe",
"profile_image_url": "https://img.clerk.com/...",
"primary_email_address_id": "idn_2NNEqL2nrIRxqh5TyNOQW8s5VX5",
"public_metadata": {},
"private_metadata": {},
"unsafe_metadata": {}
},
"event_attributes": {
"http_request": {
"client_ip": "192.168.1.1",
"user_agent": "Mozilla/5.0..."
}
},
"object": "event",
"type": "user.created"
}
Session Created
{
"data": {
"id": "sess_2NNEqL2nrIRxqh5TyNOSOqXP6XG",
"object": "session",
"client_id": "client_2NNEqL2nrIRxqh5TyNOShkHGtDp",
"user_id": "user_2NNEqL2nrIRxqh5TyNMcL3YXvVp",
"status": "active",
"last_active_at": 1654012345678,
"expire_at": 1654098745678,
"abandon_at": 1654185145678,
"created_at": 1654012345678,
"updated_at": 1654012345678
},
"object": "event",
"type": "session.created"
}
Organization Membership Created
{
"data": {
"id": "orgmem_2NNEqL2nrIRxqh5TyNPrPGHLD6b",
"object": "organization_membership",
"organization_id": "org_2NNEqL2nrIRxqh5TyNO26bgWVxV",
"user_id": "user_2NNEqL2nrIRxqh5TyNMcL3YXvVp",
"role": "member",
"permissions": ["org:posts:read", "org:posts:create"],
"created_at": 1654012345678,
"updated_at": 1654012345678
},
"object": "event",
"type": "organizationMembership.created"
}
Best Practices
1. Verify Webhook Signatures
Always verify that webhooks are coming from Clerk using Svix:
import { Webhook } from 'svix';
const webhookSecret = process.env.CLERK_WEBHOOK_SECRET;
export async function POST(request: Request) {
const payload = await request.text();
const headers = {
'svix-id': request.headers.get('svix-id')!,
'svix-timestamp': request.headers.get('svix-timestamp')!,
'svix-signature': request.headers.get('svix-signature')!,
};
const wh = new Webhook(webhookSecret);
let evt: any;
try {
evt = wh.verify(payload, headers);
} catch (err) {
console.error('Error verifying webhook:', err);
return new Response('Error occurred', { status: 400 });
}
// Handle the webhook
const eventType = evt.type;
const { id } = evt.data;
console.log(`Webhook with type ${eventType} and ID ${id} received`);
return new Response('', { status: 200 });
}
2. Handle Event Types Safely
Use TypeScript types for better type safety:
import type { WebhookEvent } from '@clerk/nextjs/server';
function handleWebhook(evt: WebhookEvent) {
switch (evt.type) {
case 'user.created':
// evt.data is typed as UserJSON
console.log('User created:', evt.data.id);
break;
case 'session.created':
// evt.data is typed as SessionJSON
console.log('Session created:', evt.data.user_id);
break;
default:
console.log('Unhandled event type:', evt.type);
}
}
3. Implement Idempotency
Prevent duplicate processing using the event ID:
const processedEvents = new Set();
export async function handleWebhook(evt: WebhookEvent) {
const eventId = evt.data.id;
if (processedEvents.has(eventId)) {
console.log(`Event ${eventId} already processed`);
return;
}
processedEvents.add(eventId);
// Process the event
await processEvent(evt);
}
4. Queue Heavy Operations
Don’t block the webhook response:
export async function POST(request: Request) {
// Verify webhook (as shown above)
// Respond immediately
const response = new Response('', { status: 200 });
// Queue for async processing
await queueWebhookProcessing(evt);
return response;
}
Common Issues
Signature Verification Failures
- Ensure you’re using the correct signing secret
- Make sure to use the raw request body (not parsed)
- Check that no middleware is modifying the request
Missing Events
- Verify all required events are selected in the dashboard
- Check that your endpoint is enabled
- Ensure your Unhook URL is correct
Delayed Events
- Some events may have a slight delay (e.g.,
user.deleted
)
- Implement proper retry logic for critical operations
- Use Clerk’s API to verify current state if needed
Environment Variables
Required environment variables for Clerk webhooks:
# Your Clerk webhook signing secret
CLERK_WEBHOOK_SECRET=whsec_...
# Optional: For API calls
CLERK_SECRET_KEY=sk_test_...
Useful Links
Support
Need help with Clerk webhooks?
Responses are generated using AI and may contain mistakes.