Clerk Integration

Receive and route Clerk webhooks for user lifecycle, authentication, organization, and session events.

Setup

1. Create a Source in Hookbase

Clerk uses Svix for webhook delivery, signing payloads with HMAC-SHA256.

curl -X POST https://api.hookbase.app/api/sources \
  -H "Authorization: Bearer whr_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Clerk Production",
    "slug": "clerk",
    "verificationConfig": {
      "type": "svix",
      "secret": "whsec_..."
    }
  }'

Save your webhook URL:

https://api.hookbase.app/ingest/{orgSlug}/clerk

2. Configure Clerk Webhook

  1. Go to Clerk Dashboard → Webhooks
  2. Click Add Endpoint
  3. Enter your Hookbase ingest URL
  4. Select the events you want to subscribe to
  5. Click Create
  6. Copy the Signing Secret (starts with whsec_) and update your Hookbase source

3. Create Routes

# Create destination for your user sync handler
curl -X POST https://api.hookbase.app/api/destinations \
  -H "Authorization: Bearer whr_your_api_key" \
  -d '{"name": "User Sync Service", "url": "https://api.myapp.com/webhooks/clerk"}'
 
# Create route
curl -X POST https://api.hookbase.app/api/routes \
  -H "Authorization: Bearer whr_your_api_key" \
  -d '{"name": "Clerk to User Sync", "sourceId": "src_...", "destinationIds": ["dst_..."]}'

Signature Verification

Clerk uses Svix for webhook delivery. The signature is sent across three headers:

HeaderDescription
svix-idUnique message ID
svix-timestampUnix timestamp (seconds)
svix-signaturev1,<base64-encoded-signature>

Hookbase verifies Svix signatures automatically:

{
  "verificationConfig": {
    "type": "svix",
    "secret": "whsec_..."
  }
}

Timestamp Tolerance

Svix signatures include a timestamp. Hookbase rejects webhooks older than 5 minutes by default to prevent replay attacks.

Common Events

user.created

Triggered when a new user signs up.

{
  "type": "user.created",
  "object": "event",
  "data": {
    "id": "user_2abc123def456",
    "object": "user",
    "first_name": "Jane",
    "last_name": "Smith",
    "email_addresses": [
      {
        "id": "idn_2abc123",
        "email_address": "[email protected]",
        "verification": {
          "status": "verified",
          "strategy": "email_code"
        }
      }
    ],
    "primary_email_address_id": "idn_2abc123",
    "username": "janesmith",
    "profile_image_url": "https://img.clerk.com/...",
    "created_at": 1709812800000,
    "updated_at": 1709812800000,
    "external_accounts": [],
    "public_metadata": {},
    "private_metadata": {},
    "unsafe_metadata": {}
  }
}

user.updated

Triggered when a user profile is modified.

{
  "type": "user.updated",
  "object": "event",
  "data": {
    "id": "user_2abc123def456",
    "object": "user",
    "first_name": "Jane",
    "last_name": "Doe",
    "email_addresses": [
      {
        "id": "idn_2abc123",
        "email_address": "[email protected]",
        "verification": { "status": "verified" }
      }
    ],
    "primary_email_address_id": "idn_2abc123",
    "public_metadata": {
      "plan": "pro"
    },
    "updated_at": 1709899200000
  }
}

user.deleted

Triggered when a user account is deleted.

{
  "type": "user.deleted",
  "object": "event",
  "data": {
    "id": "user_2abc123def456",
    "object": "user",
    "deleted": true
  }
}

organization.created

Triggered when a new organization is created.

{
  "type": "organization.created",
  "object": "event",
  "data": {
    "id": "org_2xyz789ghi012",
    "object": "organization",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "members_count": 1,
    "max_allowed_memberships": 5,
    "created_by": "user_2abc123def456",
    "public_metadata": {},
    "private_metadata": {},
    "created_at": 1709812800000,
    "updated_at": 1709812800000
  }
}

organizationMembership.created

Triggered when a user is added to an organization.

{
  "type": "organizationMembership.created",
  "object": "event",
  "data": {
    "id": "orgmem_2abc123",
    "object": "organization_membership",
    "role": "org:member",
    "organization": {
      "id": "org_2xyz789ghi012",
      "name": "Acme Corp",
      "slug": "acme-corp"
    },
    "public_user_data": {
      "user_id": "user_2def456ghi789",
      "first_name": "Bob",
      "last_name": "Wilson",
      "identifier": "[email protected]"
    },
    "created_at": 1709899200000,
    "updated_at": 1709899200000
  }
}

session.created

Triggered when a user signs in and a new session is created.

{
  "type": "session.created",
  "object": "event",
  "data": {
    "id": "sess_2abc123",
    "object": "session",
    "user_id": "user_2abc123def456",
    "status": "active",
    "last_active_at": 1709812800000,
    "expire_at": 1710417600000,
    "created_at": 1709812800000,
    "updated_at": 1709812800000
  }
}

Transform Examples

Sync New Users to Your Database

function transform(payload) {
  const user = payload.data;
  const primaryEmail = user.email_addresses.find(
    e => e.id === user.primary_email_address_id
  );
 
  return {
    external_id: user.id,
    email: primaryEmail ? primaryEmail.email_address : null,
    first_name: user.first_name,
    last_name: user.last_name,
    username: user.username,
    avatar_url: user.profile_image_url,
    metadata: user.public_metadata,
    created_at: new Date(user.created_at).toISOString()
  };
}

Slack Alert for New Sign-Ups

function transform(payload) {
  const user = payload.data;
  const primaryEmail = user.email_addresses.find(
    e => e.id === user.primary_email_address_id
  );
 
  return {
    blocks: [
      {
        type: "header",
        text: {
          type: "plain_text",
          text: "New User Sign-Up"
        }
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: `*Name:*\n${user.first_name} ${user.last_name}`
          },
          {
            type: "mrkdwn",
            text: `*Email:*\n${primaryEmail ? primaryEmail.email_address : 'N/A'}`
          },
          {
            type: "mrkdwn",
            text: `*Username:*\n${user.username || 'N/A'}`
          },
          {
            type: "mrkdwn",
            text: `*Auth Method:*\n${user.external_accounts.length > 0 ? 'OAuth' : 'Email'}`
          }
        ]
      }
    ]
  };
}

User Deletion Cleanup

function transform(payload) {
  return {
    action: "delete_user",
    clerk_user_id: payload.data.id,
    deleted_at: new Date().toISOString(),
    cleanup_tasks: [
      "remove_from_database",
      "cancel_subscriptions",
      "delete_uploaded_files",
      "send_confirmation_email"
    ]
  };
}

Filter Examples

User Events Only

{
  "name": "User Events",
  "logic": "and",
  "conditions": [
    {
      "field": "type",
      "operator": "starts_with",
      "value": "user."
    }
  ]
}

Organization Events

{
  "name": "Organization Events",
  "logic": "or",
  "conditions": [
    {
      "field": "type",
      "operator": "starts_with",
      "value": "organization."
    },
    {
      "field": "type",
      "operator": "starts_with",
      "value": "organizationMembership."
    }
  ]
}

New Sign-Ups Only

{
  "name": "New Sign-Ups",
  "logic": "and",
  "conditions": [
    {
      "field": "type",
      "operator": "equals",
      "value": "user.created"
    }
  ]
}

Specific Organization

{
  "name": "Acme Corp Events",
  "logic": "and",
  "conditions": [
    {
      "field": "type",
      "operator": "starts_with",
      "value": "organizationMembership."
    },
    {
      "field": "data.organization.slug",
      "operator": "equals",
      "value": "acme-corp"
    }
  ]
}

Headers

Clerk (Svix) webhooks include these headers:

HeaderDescription
svix-idUnique message identifier
svix-timestampUnix timestamp (seconds)
svix-signaturev1,<base64-signature>
Content-Typeapplication/json
User-AgentSvix-Webhooks

Troubleshooting

Signature Verification Failed

  1. Verify you're using the webhook signing secret (starts with whsec_)
  2. Ensure verification type is set to svix, not hmac
  3. Check that the secret matches what's shown in the Clerk dashboard
  4. Note: Clerk rotates signing secrets when you regenerate them — update your Hookbase source immediately

Missing Events

  1. Check which events are selected in Clerk's webhook endpoint settings
  2. Verify filters aren't blocking expected events
  3. Check Clerk's webhook logs (Dashboard → Webhooks → your endpoint → Message Attempts)

User Data Incomplete

  1. Some fields are only populated based on your Clerk instance configuration
  2. username is null unless you enable usernames in Clerk settings
  3. external_accounts only populated when users sign in via OAuth
  4. private_metadata and unsafe_metadata are not included in webhook payloads by default