Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Jackson57279/zapdev/llms.txt
Use this file to discover all available pages before exploring further.
Overview
ZapDev uses Clerk webhooks to receive real-time notifications about user authentication events, profile updates, and organization changes. This ensures the application stays synchronized with Clerk’s user management system.
Endpoint URL
Configuration
Environment Variables
Add this to your .env.local file:
CLERK_WEBHOOK_SECRET=whsec_your_webhook_secret
Clerk Dashboard Setup
- Log in to your Clerk Dashboard
- Select your application
- Navigate to Webhooks in the sidebar
- Click Add Endpoint
- Enter your endpoint URL:
https://yourdomain.com/api/webhooks/clerk
- Select the events you want to receive:
user.created
user.updated
organization.created
organization.updated
- Copy the Signing Secret to your environment variables
Webhook Verification
All incoming webhooks are verified using the Svix library (Clerk’s webhook provider):
import { Webhook } from "svix";
import { headers } from "next/headers";
import { WebhookEvent } from "@clerk/nextjs/server";
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
if (!WEBHOOK_SECRET) {
throw new Error(
"Please add CLERK_WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local"
);
}
const headerPayload = await headers();
const svix_id = headerPayload.get("svix-id");
const svix_timestamp = headerPayload.get("svix-timestamp");
const svix_signature = headerPayload.get("svix-signature");
if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response("Error occured -- no svix headers", {
status: 400,
});
}
const payload = await req.json();
const body = JSON.stringify(payload);
const wh = new Webhook(WEBHOOK_SECRET);
let evt: WebhookEvent;
try {
evt = wh.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
}) as WebhookEvent;
} catch (err) {
console.error("Error verifying webhook:", err);
return new Response("Error occured", {
status: 400,
});
}
See implementation in src/app/api/webhooks/clerk/route.ts:7-44.
Supported Events
User Events
user.created
Fired when a new user signs up or is created in Clerk.
Payload Example:
{
"type": "user.created",
"data": {
"id": "user_2abc123def456",
"email_addresses": [
{
"id": "idn_xyz789",
"email_address": "user@example.com",
"verification": {
"status": "verified"
}
}
],
"first_name": "John",
"last_name": "Doe",
"username": "johndoe",
"created_at": 1709467200000,
"updated_at": 1709467200000
}
}
Current Processing:
case "user.created": {
const user = evt.data;
console.log(`User ${user.id}: ${user.first_name} ${user.last_name}`);
break;
}
See implementation in src/app/api/webhooks/clerk/route.ts:52-56.
Note: User data is currently logged only. Future implementations may sync user profiles to Convex database or trigger onboarding workflows.
user.updated
Fired when user profile information changes (name, email, avatar, etc.).
Payload Example:
{
"type": "user.updated",
"data": {
"id": "user_2abc123def456",
"email_addresses": [
{
"id": "idn_xyz789",
"email_address": "newemail@example.com",
"verification": {
"status": "verified"
}
}
],
"first_name": "Jane",
"last_name": "Smith",
"updated_at": 1709553600000
}
}
Current Processing:
case "user.updated": {
const user = evt.data;
console.log(`User ${user.id}: ${user.first_name} ${user.last_name}`);
break;
}
See implementation in src/app/api/webhooks/clerk/route.ts:52-56.
Organization Events
organization.created
Fired when a new organization is created.
Payload Example:
{
"type": "organization.created",
"data": {
"id": "org_abc123xyz789",
"name": "Acme Corporation",
"slug": "acme-corp",
"created_at": 1709467200000,
"updated_at": 1709467200000,
"members_count": 1
}
}
Current Processing:
case "organization.created": {
const organization = evt.data;
console.log(`Organization ${organization.id}: ${organization.name}`);
break;
}
See implementation in src/app/api/webhooks/clerk/route.ts:59-63.
organization.updated
Fired when organization details change (name, settings, etc.).
Payload Example:
{
"type": "organization.updated",
"data": {
"id": "org_abc123xyz789",
"name": "Acme Inc.",
"slug": "acme-inc",
"updated_at": 1709553600000,
"members_count": 5
}
}
Current Processing:
case "organization.updated": {
const organization = evt.data;
console.log(`Organization ${organization.id}: ${organization.name}`);
break;
}
See implementation in src/app/api/webhooks/clerk/route.ts:59-63.
Event Processing
The webhook handler uses a switch statement to route events:
const eventType = evt.type;
console.log(`Webhook with type of ${eventType}`);
try {
switch (eventType) {
case "user.created":
case "user.updated": {
const user = evt.data;
console.log(`User ${user.id}: ${user.first_name} ${user.last_name}`);
break;
}
case "organization.created":
case "organization.updated": {
const organization = evt.data;
console.log(`Organization ${organization.id}: ${organization.name}`);
break;
}
default:
console.log(`Unhandled webhook event type: ${eventType}`);
}
return new Response("", { status: 200 });
} catch (error) {
console.error("Error processing webhook:", error);
return new Response("Error processing webhook", { status: 500 });
}
See implementation in src/app/api/webhooks/clerk/route.ts:46-75.
Testing Webhooks
Using Clerk Dashboard
- Go to Webhooks in your Clerk Dashboard
- Click on your webhook endpoint
- Navigate to the Testing tab
- Select an event type (e.g.,
user.created)
- Click Send Example
- Verify the webhook was received in your application logs
Local Development with ngrok
For local testing, expose your development server:
# Start your Next.js dev server
bun run dev
# In another terminal, start ngrok
ngrok http 3000
Then:
- Copy the ngrok HTTPS URL (e.g.,
https://abc123.ngrok.io)
- Update your Clerk webhook endpoint to
https://abc123.ngrok.io/api/webhooks/clerk
- Trigger test events from Clerk Dashboard
- Monitor your local server logs
Manual Testing with curl
curl -X POST https://yourdomain.com/api/webhooks/clerk \
-H "Content-Type: application/json" \
-H "svix-id: msg_abc123" \
-H "svix-timestamp: 1709467200" \
-H "svix-signature: v1,signature_here" \
-d '{
"type": "user.created",
"data": {
"id": "user_test123",
"first_name": "Test",
"last_name": "User"
}
}'
Note: Manual testing requires generating a valid Svix signature. Use Clerk’s testing tools instead.
Error Handling
The webhook handler includes comprehensive error handling:
if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response("Error occured -- no svix headers", {
status: 400,
});
}
Verification Failure
try {
evt = wh.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
}) as WebhookEvent;
} catch (err) {
console.error("Error verifying webhook:", err);
return new Response("Error occured", {
status: 400,
});
}
Processing Errors
try {
// Process event
switch (eventType) {
// ... event handlers
}
return new Response("", { status: 200 });
} catch (error) {
console.error("Error processing webhook:", error);
return new Response("Error processing webhook", { status: 500 });
}
Security Best Practices
- Always verify signatures - Never process webhooks without signature verification
- Use HTTPS - Clerk requires HTTPS endpoints in production
- Validate event types - Only process expected event types
- Log errors - Monitor webhook failures for debugging
- Idempotency - Design event handlers to be idempotent (safe to retry)
- Rate limiting - Consider implementing rate limits on your webhook endpoint
Common Issues
Webhook Secret Not Found
Error:
Error: Please add CLERK_WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local
Solution: Add CLERK_WEBHOOK_SECRET to your environment variables.
Signature Verification Failed
Error:
Error verifying webhook: signature verification failed
Solutions:
- Verify the webhook secret matches your Clerk Dashboard
- Check that the request body is not modified before verification
- Ensure you’re using the raw request body (not parsed JSON)
Error:
Error occured -- no svix headers
Solution: Verify the request is coming from Clerk with proper headers (svix-id, svix-timestamp, svix-signature).
Future Enhancements
Potential improvements to the webhook handler:
- User Profile Sync - Store user data in Convex for faster access
- Organization Management - Sync organization data and team memberships
- Event Logging - Store webhook events using
api.webhooks.logWebhookEvent
- Onboarding Triggers - Trigger welcome emails or onboarding flows for new users
- Analytics - Track user signup and profile update metrics
- Polar Integration - Link Clerk users with Polar customers for subscription management
- Webhook handler:
src/app/api/webhooks/clerk/route.ts
- Clerk configuration:
src/middleware.ts (authentication middleware)
- Event logging utilities:
convex/webhooks.ts
Additional Resources