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",
"provider": "svix",
"signingSecret": "whsec_..."
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/clerk2. Configure Clerk Webhook
- Go to Clerk Dashboard → Webhooks
- Click Add Endpoint
- Enter your Hookbase ingest URL
- Select the events you want to subscribe to
- Click Create
- On the endpoint's settings page, copy the Signing Secret (starts with
whsec_) and paste it into your Hookbase source's Signing Secret field (signingSecretin the API)
Warning
Clerk generates the Signing Secret; Hookbase cannot. Don't leave the field empty and let Hookbase
fill in a random value — Clerk never signs with it, so every event would arrive with
signature_valid: false. If you created the source before the Clerk endpoint existed, update the
source with the whsec_ value once Clerk shows it.
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", "slug": "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_...", "destinationId": "dst_..."}'Signature Verification
Clerk uses Svix for webhook delivery. The signature is sent across three headers:
| Header | Description |
|---|---|
svix-id | Unique message ID |
svix-timestamp | Unix timestamp (seconds) |
svix-signature | v1,<base64-encoded-signature> |
Hookbase verifies Svix signatures automatically:
{
"provider": "svix",
"signingSecret": "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": "jane@example.com",
"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": "jane@example.com",
"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": "bob@example.com"
},
"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:
| Header | Description |
|---|---|
svix-id | Unique message identifier |
svix-timestamp | Unix timestamp (seconds) |
svix-signature | v1,<base64-signature> |
Content-Type | application/json |
User-Agent | Svix-Webhooks |
Hookbase does not forward these to your destination. A delivery is a fresh request with
Hookbase's own X-Delivery-ID and X-Event-ID plus the headers and auth you configured on the
destination — nothing Clerk sent reaches your handler as a header. See
what headers a delivery carries.
Troubleshooting
Signature Verification Failed
- Verify you're using the endpoint's Signing Secret (starts with
whsec_), copied from Clerk — not a Clerk API secret key (sk_...) or a value Hookbase generated - Confirm the source's
providerissvix(orclerk, or the canonical spelling,standard-webhooks). There is no separate "verification type" field —provideris the only thing that selects a scheme — andhmacis not a value Hookbase accepts:POST /api/sourcesrejects it with a400listing the valid providers. If the source was created beforesvixwas accepted, it is probably oncustom, which reads three header names Clerk does not send and cannot verify this traffic - Check that the secret matches what's shown in the Clerk dashboard
- Note: Clerk rotates signing secrets when you regenerate them — update your Hookbase source immediately
Missing Events
- Check which events are selected in Clerk's webhook endpoint settings
- Verify filters aren't blocking expected events
- Check Clerk's webhook logs (Dashboard → Webhooks → your endpoint → Message Attempts)
User Data Incomplete
- Some fields are only populated based on your Clerk instance configuration
usernameis null unless you enable usernames in Clerk settingsexternal_accountsonly populated when users sign in via OAuthprivate_metadataandunsafe_metadataare not included in webhook payloads by default