# Clerk Webhook Setup Guide

This guide explains how to set up Clerk webhooks to automatically sync user data between Clerk and your local database.

## Overview

When users sign up, update their profile, or delete their account in Clerk, webhooks automatically sync these changes to your local database. This ensures your database always has up-to-date user information without manual intervention.

## Architecture

```
Clerk Event (user.created/updated/deleted)
    ↓
Clerk Webhook → Your API Endpoint (/api/webhooks)
    ↓
Webhook Handler (verifies signature)
    ↓
Database Sync (insert/update/delete user)
```

## What Gets Synced

The webhook handler syncs the following user data:

- **User ID** - Clerk user ID (primary key)
- **Email** - Primary email address
- **First Name** - User's first name
- **Last Name** - User's last name
- **Role** - From `public_metadata.role` (defaults to 'client')
- **Phone** - Optional phone number
- **Timestamps** - Created/updated timestamps

## Setup Instructions

### 1. Get Your Webhook Secret

#### Development (Local Testing)

1. Install Clerk CLI:
   ```bash
   npm install -g @clerk/clerk-cli
   ```

2. Login to Clerk CLI:
   ```bash
   clerk login
   ```

3. Start webhook forwarding (in a new terminal):
   ```bash
   clerk listen --forward-url http://localhost:3000/api/webhooks
   ```

   This will output a webhook secret like: `whsec_xxxxxxxxxxxxx`

#### Production

1. Go to [Clerk Dashboard](https://dashboard.clerk.com)
2. Select your application
3. Navigate to **Webhooks** in the left sidebar
4. Click **Add Endpoint**
5. Enter your production URL: `https://yourdomain.com/api/webhooks`
6. Select the events to subscribe to:
   - `user.created`
   - `user.updated`
   - `user.deleted`
7. Copy the **Signing Secret**

### 2. Add Environment Variables

Add the webhook secret to your environment variables:

**For Admin App (`apps/admin/.env.local`):**

```env
# Clerk Configuration
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxx

# Webhook Secret (from step 1)
CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
```

### 3. Update Database Schema

The database schema has been updated to make the `password` field optional since Clerk handles authentication:

```typescript
export const users = sqliteTable("users", {
  id: text("id").primaryKey(),
  email: text("email").notNull().unique(),
  firstName: text("first_name").notNull(),
  lastName: text("last_name").notNull(),
  password: text("password"), // Optional - Clerk handles auth
  role: text("role", { enum: ["admin", "trainer", "client"] })
    .notNull()
    .default("client"),
  phone: text("phone"),
  createdAt: integer("created_at", { mode: "timestamp" })
    .notNull()
    .$defaultFn(() => new Date()),
  updatedAt: integer("updated_at", { mode: "timestamp" })
    .notNull()
    .$defaultFn(() => new Date()),
});
```

### 4. Run Database Migration

Apply the schema changes to your database:

```bash
cd packages/database
npm run db:push
```

### 5. Test the Webhook

#### Using Clerk CLI (Development)

1. Start your Next.js dev server:
   ```bash
   cd apps/admin
   npm run dev
   ```

2. In another terminal, start Clerk webhook forwarding:
   ```bash
   clerk listen --forward-url http://localhost:3000/api/webhooks
   ```

3. Create a test user in Clerk Dashboard or through your app

4. Check your terminal logs - you should see:
   ```
   ✅ User user_xxxxx created in database
   ```

#### Using Clerk Dashboard (Production)

1. Go to your webhook endpoint in Clerk Dashboard
2. Click **Send Test Event**
3. Select `user.created` event type
4. Click **Send**
5. Check the webhook logs for success (200 status)

## Webhook Events Handled

### `user.created`

Triggered when a new user signs up.

**Action:** Inserts a new user record into the database.

**Payload Example:**
```json
{
  "type": "user.created",
  "data": {
    "id": "user_2abc123",
    "email_addresses": [
      {
        "id": "idn_xyz789",
        "email_address": "user@example.com"
      }
    ],
    "primary_email_address_id": "idn_xyz789",
    "first_name": "John",
    "last_name": "Doe",
    "public_metadata": {
      "role": "client"
    }
  }
}
```

### `user.updated`

Triggered when a user updates their profile.

**Action:** Updates the user record in the database.

**Fields Updated:**
- Email (if changed)
- First name
- Last name
- Role (from public_metadata)
- Updated timestamp

### `user.deleted`

Triggered when a user is deleted from Clerk.

**Action:** Deletes the user from the database (cascades to related records).

## Setting User Roles

To assign roles to users, update their `public_metadata` in Clerk:

### Option 1: Clerk Dashboard

1. Go to **Users** in Clerk Dashboard
2. Select a user
3. Scroll to **Public Metadata**
4. Add:
   ```json
   {
     "role": "admin"
   }
   ```
5. Click **Save**

### Option 2: Programmatically (Backend)

```typescript
import { clerkClient } from '@clerk/nextjs/server';

async function setUserRole(userId: string, role: 'admin' | 'trainer' | 'client') {
  await clerkClient.users.updateUser(userId, {
    publicMetadata: { role },
  });
}
```

### Option 3: On Sign-Up (Custom Flow)

```typescript
// In your sign-up flow
await signUp.create({
  emailAddress,
  password,
  firstName,
  lastName,
  unsafeMetadata: {
    // Store temporarily during sign-up
    role: 'client',
  },
});

// After email verification, move to public_metadata
await user.update({
  publicMetadata: {
    role: user.unsafeMetadata.role,
  },
});
```

## Security

### Webhook Signature Verification

The webhook handler **always verifies** the signature of incoming requests using Svix:

```typescript
const wh = new Webhook(WEBHOOK_SECRET);
const evt = wh.verify(body, {
  'svix-id': svix_id,
  'svix-timestamp': svix_timestamp,
  'svix-signature': svix_signature,
});
```

This ensures:
- ✅ Requests come from Clerk (not malicious actors)
- ✅ Payload hasn't been tampered with
- ✅ Request is recent (prevents replay attacks)

### Best Practices

1. **Keep your webhook secret secure** - Never commit it to version control
2. **Use HTTPS in production** - Webhooks should only be sent over HTTPS
3. **Monitor webhook logs** - Check for failed webhooks regularly
4. **Handle errors gracefully** - The handler returns appropriate status codes
5. **Test thoroughly** - Use Clerk CLI for local testing before deploying

## Troubleshooting

### Webhook Returns 400 Error

**Problem:** Missing Svix headers or invalid signature

**Solutions:**
- Verify `CLERK_WEBHOOK_SECRET` is set correctly
- Check that you're using the correct webhook secret for your environment
- Ensure the endpoint URL in Clerk Dashboard is correct

### User Not Created in Database

**Problem:** Webhook received but user not inserted

**Solutions:**
- Check server logs for errors
- Verify database connection is working
- Ensure user doesn't already exist (duplicate email)
- Check that schema migration was applied

### Webhook Not Received

**Problem:** Events in Clerk but no webhook calls

**Solutions:**
- Verify webhook endpoint is configured in Clerk Dashboard
- Check that events are subscribed (`user.created`, `user.updated`, `user.deleted`)
- Ensure your server is publicly accessible (for production)
- For local dev, ensure `clerk listen` is running

### Role Not Set Correctly

**Problem:** User created but role is always 'client'

**Solutions:**
- Check that `public_metadata.role` is set in Clerk
- Verify the role value is one of: `admin`, `trainer`, `client`
- Use `unsafeMetadata` during sign-up, then move to `publicMetadata` after verification

## Monitoring

### View Webhook Logs in Clerk Dashboard

1. Go to **Webhooks** in Clerk Dashboard
2. Click on your endpoint
3. View recent webhook attempts, status codes, and payloads
4. Use **Retry** button for failed webhooks

### Check Your Server Logs

The webhook handler logs important events:

```
✅ User user_abc123 created in database
✅ User user_abc123 updated in database
✅ User user_abc123 deleted from database
```

Errors are also logged:
```
Error verifying webhook: [details]
Error processing webhook: [details]
```

## Migration Strategy

If you have existing users in your database (from before Clerk integration):

### Option 1: Import to Clerk

Use Clerk's user import feature or API to bulk import existing users.

### Option 2: Dual Authentication

Keep both auth systems temporarily and gradually migrate users:

```typescript
// Check if user exists in Clerk first
const clerkUser = await clerkClient.users.getUser(userId);

// If not, check local database
if (!clerkUser) {
  const localUser = await db.query.users.findFirst({
    where: eq(users.email, email),
  });
  
  if (localUser && localUser.password) {
    // Migrate to Clerk
    await clerkClient.users.createUser({
      emailAddress: [localUser.email],
      firstName: localUser.firstName,
      lastName: localUser.lastName,
      publicMetadata: { role: localUser.role },
    });
  }
}
```

## Next Steps

After setting up webhooks:

1. ✅ **Test user creation** - Sign up a new user and verify database sync
2. ✅ **Test user updates** - Update profile and verify sync
3. ✅ **Set up roles** - Assign admin/trainer roles to appropriate users
4. ✅ **Monitor webhooks** - Check Clerk Dashboard for webhook health
5. ✅ **Document for team** - Share this guide with your team

## Related Documentation

- [Clerk Webhooks Documentation](https://clerk.com/docs/integrations/webhooks/overview)
- [Svix Webhook Verification](https://docs.svix.com/receiving/verifying-payloads/how)
- [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
- [Drizzle ORM](https://orm.drizzle.team/docs/overview)

## Support

If you encounter issues:

1. Check this guide's troubleshooting section
2. Review Clerk webhook logs in the dashboard
3. Check your server logs for errors
4. Consult the main `TROUBLESHOOTING.md` file
5. Contact Clerk support or open a GitHub issue

---

**Webhook Handler Location:** `apps/admin/src/app/api/webhooks/route.ts`

**Last Updated:** 2024