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

  1. Get your Unhook URL: https://unhook.sh/wh_YOUR_ID
  2. Configure in Clerk Dashboard: dashboard.clerk.com/webhooks
  3. Start receiving events locally: unhook listen

Configuration Steps

1. Access Clerk Webhook Settings

Navigate to the Clerk Dashboard:

2. Create a New Endpoint

  1. Click “Add Endpoint”
  2. Enter your Unhook URL:
    https://unhook.sh/wh_YOUR_ID
    
  3. 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

4. Configure Webhook Settings

After selecting events:

  1. Enable the endpoint with the toggle switch
  2. Copy the Signing Secret - you’ll need this to verify webhooks
  3. Click “Create” to save

Webhook Verification

Clerk uses Svix to sign webhooks. To verify signatures:

  1. After creating the endpoint, copy the Signing Secret
  2. It will look like: whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
  3. Add it to your environment variables:
    CLERK_WEBHOOK_SECRET=whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
    

Testing Webhooks

Using Clerk Dashboard

  1. Go to your webhook endpoint in the Dashboard
  2. Click on the endpoint
  3. Navigate to the “Testing” tab
  4. Select an event type
  5. 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_...

Support

Need help with Clerk webhooks?