Files
2023-09-11 21:23:37 -05:00

87 lines
2.1 KiB
TypeScript

import type { WebhookEvent } from '@clerk/backend';
import { httpRouter } from 'convex/server';
import { Webhook } from 'svix';
import { internal } from './_generated/api';
import { httpAction } from './_generated/server';
async function validateRequest(
req: Request
): Promise<WebhookEvent | undefined> {
const payloadString = await req.text();
const svixHeaders = {
'svix-id': req.headers.get('svix-id')!,
'svix-timestamp': req.headers.get('svix-timestamp')!,
'svix-signature': req.headers.get('svix-signature')!,
};
const webhookSecret = process.env.CLERK_WEBHOOK_SECRET;
if (webhookSecret === undefined) {
throw Error('CLERK_WEBHOOK_SECRET is undefined');
}
const wh = new Webhook(webhookSecret);
let evt: Event | null = null;
try {
evt = wh.verify(payloadString, svixHeaders) as Event;
} catch (error) {
console.error(error);
return;
}
return evt as unknown as WebhookEvent;
}
const handleClerkWebhook = httpAction(async (ctx, request) => {
const event = await validateRequest(request);
if (!event) {
return new Response('Error occurred', {
status: 400,
});
}
switch (event.type) {
case 'user.created':
case 'user.updated': {
const existingUser = await ctx.runQuery(internal.users.getUser, {
subject: event.data.id,
});
if (existingUser && event.type === 'user.created') {
console.warn('Overwriting user', event.data.id, 'with', event.data);
}
console.log('creating/updating user', event.data.id);
await ctx.runMutation(internal.users.updateOrCreateUser, {
clerkUser: event.data,
});
break;
}
case 'user.deleted': {
const id = event.data.id!;
await ctx.runMutation(internal.users.deleteUser, { id });
break;
}
default: {
console.warn('ignored Clerk webhook event', event.type);
}
}
return new Response(null, {
status: 200,
});
});
const http = httpRouter();
http.route({
path: '/clerk-users-webhook',
method: 'POST',
handler: handleClerkWebhook,
});
export default http;