import { Router, Request, Response } from 'express' import { Webhook } from 'svix' import { handleInvitationAccepted } from '../services/teamService' import { EmployeeRole } from '@prisma/client' const router = Router() /** * POST /webhooks/clerk * * Handles Clerk webhook events. Registered in the Clerk dashboard. * Uses svix signature verification — raw body must be preserved * (use express.raw() before this route, not express.json()). * * Events handled: * - user.created → when an invited employee accepts and creates their account */ router.post( '/clerk', async (req: Request, res: Response) => { const webhookSecret = process.env.CLERK_WEBHOOK_SECRET if (!webhookSecret) { console.error('CLERK_WEBHOOK_SECRET is not set') return res.status(500).json({ error: 'Webhook secret not configured' }) } // Verify signature const wh = new Webhook(webhookSecret) let event: any try { event = wh.verify(req.body, { 'svix-id': req.headers['svix-id'] as string, 'svix-timestamp': req.headers['svix-timestamp'] as string, 'svix-signature': req.headers['svix-signature'] as string, }) } catch (err) { console.error('Clerk webhook signature verification failed:', err) return res.status(400).json({ error: 'Invalid webhook signature' }) } const { type, data } = event // ── user.created ────────────────────────────────────────── // Fired when an invited employee accepts their invite and creates // their Clerk account. publicMetadata contains companyId + role // that we embedded when creating the invitation. if (type === 'user.created') { const clerkUserId: string = data.id const email: string = data.email_addresses?.[0]?.email_address ?? '' const meta = data.public_metadata ?? {} const companyId: string | undefined = meta.companyId const role: EmployeeRole | undefined = meta.role if (companyId && role && email) { try { const employee = await handleInvitationAccepted( clerkUserId, email, companyId, role ) if (employee) { console.log( `Employee activated: ${employee.firstName} ${employee.lastName} (${employee.id})` ) } } catch (err) { // Log but do not return 500 — Clerk will retry on 5xx console.error('Failed to activate employee from Clerk webhook:', err) } } } res.json({ received: true }) } ) export default router