Skip to content

Instantly share code, notes, and snippets.

@bmorrisondev
Created April 23, 2024 20:38
Show Gist options
  • Save bmorrisondev/edcbb2996f99d6633846d3223863e41c to your computer and use it in GitHub Desktop.
Save bmorrisondev/edcbb2996f99d6633846d3223863e41c to your computer and use it in GitHub Desktop.
import { WebhookEvent } from "@clerk/nextjs/server";
import { headers } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import { Webhook } from "svix"
type WebhooksHandler = {
config: WebhookRegistrationConfig
POST: (req: NextRequest) => Promise<NextResponse | Response>
}
export function createWebhooksHandler(config: WebhookRegistrationConfig): WebhooksHandler {
return {
config: config,
POST: async (req: NextRequest) => await handleWebhooks(config, req)
}
}
export type WebhookRegistrationConfig = {
secret?: string
urlPrefix?: string
onUserCreated?: (event: WebhookEvent) => Promise<void | NextResponse>;
onUserUpdated?: (event: WebhookEvent) => Promise<void | NextResponse>;
}
export async function handleWebhooks(config: WebhookRegistrationConfig, req: NextRequest): Promise<NextResponse | Response> {
// If the request is not a POST request or does not start with the webhook URL, return a 404
const WEBHOOK_SECRET = config.secret ? config.secret : process.env.WEBHOOK_SECRET
if (!WEBHOOK_SECRET) {
throw new Error('Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local')
}
const headerPayload = 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 occurred -- no svix headers', {
status: 400
})
}
// Get the body
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
})
}
switch(evt.type) {
case 'user.created':
if (config.onUserCreated) {
return await _handler(evt, config.onUserCreated)
}
break;
case 'user.updated':
if (config.onUserUpdated) {
return await _handler(evt, config.onUserUpdated)
}
break;
}
// If we don't have a handler for the event, return a 404
return NextResponse.next()
}
async function _handler(event: WebhookEvent, callback: Function): Promise<NextResponse | Response> {
let response = await callback(event)
if(response != undefined) {
return response
} else {
return new Response('', { status: 200 })
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment