# Clerk Webhooks

## When to Use This Skill

* Setting up Clerk webhook handlers
* Debugging signature verification failures
* Understanding Clerk event types and payloads
* Handling user, session, or organization events

## Essential Code (USE THIS)

### Express Webhook Handler

Clerk uses the [Standard Webhooks](https://www.standardwebhooks.com/) protocol (Clerk sends `svix-*` headers; same format). Use the `standardwebhooks` npm package:

```javascript
const express = require('express');
const { Webhook } = require('standardwebhooks');

const app = express();

// CRITICAL: Use express.raw() for webhook endpoint - verification needs raw body
app.post('/webhooks/clerk',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const secret = process.env.CLERK_WEBHOOK_SECRET || process.env.CLERK_WEBHOOK_SIGNING_SECRET;
    if (!secret || !secret.startsWith('whsec_')) {
      return res.status(500).json({ error: 'Server configuration error' });
    }
    const svixId = req.headers['svix-id'];
    const svixTimestamp = req.headers['svix-timestamp'];
    const svixSignature = req.headers['svix-signature'];
    if (!svixId || !svixTimestamp || !svixSignature) {
      return res.status(400).json({ error: 'Missing required webhook headers' });
    }
    // standardwebhooks expects webhook-* header names; Clerk sends svix-* (same protocol)
    const headers = {
      'webhook-id': svixId,
      'webhook-timestamp': svixTimestamp,
      'webhook-signature': svixSignature
    };
    try {
      const wh = new Webhook(secret);
      const event = wh.verify(req.body, headers);
      if (!event) return res.status(400).json({ error: 'Invalid payload' });
      switch (event.type) {
        case 'user.created': console.log('User created:', event.data.id); break;
        case 'user.updated': console.log('User updated:', event.data.id); break;
        case 'session.created': console.log('Session created:', event.data.user_id); break;
        case 'organization.created': console.log('Organization created:', event.data.id); break;
        default: console.log('Unhandled:', event.type);
      }
      res.status(200).json({ success: true });
    } catch (err) {
      res.status(400).json({ error: err.name === 'WebhookVerificationError' ? err.message : 'Webhook verification failed' });
    }
  }
);

```

### Python (FastAPI) Webhook Handler

```python
import os
import hmac
import hashlib
import base64
from fastapi import FastAPI, Request, HTTPException
from time import time

webhook_secret = os.environ.get("CLERK_WEBHOOK_SECRET")

@app.post("/webhooks/clerk")
async def clerk_webhook(request: Request):
    # Get Svix headers
    svix_id = request.headers.get("svix-id")
    svix_timestamp = request.headers.get("svix-timestamp")
    svix_signature = request.headers.get("svix-signature")

    if not all([svix_id, svix_timestamp, svix_signature]):
        raise HTTPException(status_code=400, detail="Missing required Svix headers")

    # Get raw body
    body = await request.body()

    # Manual signature verification
    signed_content = f"{svix_id}.{svix_timestamp}.{body.decode()}"

    # Extract base64 secret after 'whsec_' prefix
    secret_bytes = base64.b64decode(webhook_secret.split('_')[1])
    expected_signature = base64.b64encode(
        hmac.new(secret_bytes, signed_content.encode(), hashlib.sha256).digest()
    ).decode()

    # Svix can send multiple signatures, check each one
    signatures = [sig.split(',')[1] for sig in svix_signature.split(' ')]
    if expected_signature not in signatures:
        raise HTTPException(status_code=400, detail="Invalid signature")

    # Check timestamp (5-minute window)
    current_time = int(time())
    if current_time - int(svix_timestamp) > 300:
        raise HTTPException(status_code=400, detail="Timestamp too old")

    # Handle event...
    return {"success": True}

```

> For complete working examples with tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/clerk-webhooks/examples/express/) - Full Express implementation
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/clerk-webhooks/examples/nextjs/) - Next.js App Router implementation
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/clerk-webhooks/examples/fastapi/) - Python FastAPI implementation

## Common Event Types

| Event | Description |
| --- | --- |
| `user.created` | New user account created |
| `user.updated` | User profile or metadata updated |
| `user.deleted` | User account deleted |
| `session.created` | User signed in |
| `session.ended` | User signed out |
| `session.removed` | Session revoked |
| `organization.created` | New organization created |
| `organization.updated` | Organization settings updated |
| `organizationMembership.created` | User added to organization |
| `organizationInvitation.created` | Invite sent to join organization |

> For full event reference, see [Clerk Webhook Events](https://clerk.com/docs/integrations/webhooks/overview#event-types) and [Dashboard → Webhooks → Event Catalog](https://dashboard.clerk.com/~/webhooks).

## Environment Variables

```bash
# Official name (used by @clerk/nextjs and Clerk docs)
CLERK_WEBHOOK_SIGNING_SECRET=whsec_xxxxx

# Alternative name (used in this skill's examples)
CLERK_WEBHOOK_SECRET=whsec_xxxxx

```

From Clerk Dashboard → Webhooks → your endpoint → Signing Secret.

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 clerk --path /webhooks/clerk

```

Use the tunnel URL in Clerk Dashboard when adding your endpoint. For production, set your live URL and copy the signing secret to production env vars.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/clerk-webhooks/references/overview.md) - Clerk webhook concepts
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/clerk-webhooks/references/setup.md) - Dashboard configuration
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/clerk-webhooks/references/verification.md) - Signature verification details
* [references/patterns.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/clerk-webhooks/references/patterns.md) - Quick start, when to sync, key patterns, common pitfalls