Need clerk webhook example

I have been trying to configure a webhook to be called as soon an organization has been created and the webhook should call a prisma query to do a DB entry but I couldnt find any example that would help me configure this. I am using pages router.
5 Replies
whatplan
whatplan2y ago
I know for sure there are examples in the clerk server (maybe even in the clerk docs) maybe even in here too just search for them and see what you can find here is what I have in a code base I wrote 3 months ago following something from the clerk server
import type { IncomingHttpHeaders } from "http";
import type { NextApiRequest, NextApiResponse } from "next";
import type { WebhookRequiredHeaders } from "svix";
import type { User } from "@clerk/nextjs/dist/api";
import { Webhook } from "svix";
import { prisma } from "~/server/clients/db";
import { env } from "~/env.mjs";

type UnwantedKeys =
| "emailAddresses"
| "firstName"
| "lastName"
| "primaryEmailAddressId"
| "primaryPhoneNumberId"
| "phoneNumbers";

interface UserInterface extends Omit<User, UnwantedKeys> {
email_addresses: {
email_address: string;
id: string;
}[];
primary_email_address_id: string;
first_name: string;
last_name: string;
primary_phone_number_id: string;
phone_numbers: {
phone_number: string;
id: string;
}[];
}

const webhookSecret: string = env.CLERK_WEBHOOK_SECRET || "";

export default async function handler(
req: NextApiRequestWithSvixRequiredHeaders,
res: NextApiResponse
) {
const payload = JSON.stringify(req.body);
const headers = req.headers;
const wh = new Webhook(webhookSecret);
let evt: Event | null = null;
try {
evt = wh.verify(payload, headers) as Event;
} catch (_) {
return res.status(400).json({ message: "Invalid signature" });
}
const { id } = evt.data;
// Handle the webhook
const eventType: EventType = evt.type;
if (eventType === "user.created" || eventType === "user.updated") {
const { email_addresses, primary_email_address_id, username } = evt.data;
const emailObject = email_addresses?.find((email) => {
return email.id === primary_email_address_id;
});
if (!emailObject || !username) {
return res.status(400).json({ message: "Missing email or username" });
}
await prisma.account.upsert({
where: { clerkId: id },
update: {
username: username,
email: emailObject.email_address,
},
create: {
clerkId: id,
username: username,
email: emailObject.email_address,
},
});
}
console.log(`User ${id} was ${eventType}`);
res.status(201).json({ message: "OK" });
}

type NextApiRequestWithSvixRequiredHeaders = NextApiRequest & {
headers: IncomingHttpHeaders & WebhookRequiredHeaders;
};

type Event = {
data: UserInterface;
object: "event";
type: EventType;
};

type EventType = "user.created" | "user.updated" | "*";
import type { IncomingHttpHeaders } from "http";
import type { NextApiRequest, NextApiResponse } from "next";
import type { WebhookRequiredHeaders } from "svix";
import type { User } from "@clerk/nextjs/dist/api";
import { Webhook } from "svix";
import { prisma } from "~/server/clients/db";
import { env } from "~/env.mjs";

type UnwantedKeys =
| "emailAddresses"
| "firstName"
| "lastName"
| "primaryEmailAddressId"
| "primaryPhoneNumberId"
| "phoneNumbers";

interface UserInterface extends Omit<User, UnwantedKeys> {
email_addresses: {
email_address: string;
id: string;
}[];
primary_email_address_id: string;
first_name: string;
last_name: string;
primary_phone_number_id: string;
phone_numbers: {
phone_number: string;
id: string;
}[];
}

const webhookSecret: string = env.CLERK_WEBHOOK_SECRET || "";

export default async function handler(
req: NextApiRequestWithSvixRequiredHeaders,
res: NextApiResponse
) {
const payload = JSON.stringify(req.body);
const headers = req.headers;
const wh = new Webhook(webhookSecret);
let evt: Event | null = null;
try {
evt = wh.verify(payload, headers) as Event;
} catch (_) {
return res.status(400).json({ message: "Invalid signature" });
}
const { id } = evt.data;
// Handle the webhook
const eventType: EventType = evt.type;
if (eventType === "user.created" || eventType === "user.updated") {
const { email_addresses, primary_email_address_id, username } = evt.data;
const emailObject = email_addresses?.find((email) => {
return email.id === primary_email_address_id;
});
if (!emailObject || !username) {
return res.status(400).json({ message: "Missing email or username" });
}
await prisma.account.upsert({
where: { clerkId: id },
update: {
username: username,
email: emailObject.email_address,
},
create: {
clerkId: id,
username: username,
email: emailObject.email_address,
},
});
}
console.log(`User ${id} was ${eventType}`);
res.status(201).json({ message: "OK" });
}

type NextApiRequestWithSvixRequiredHeaders = NextApiRequest & {
headers: IncomingHttpHeaders & WebhookRequiredHeaders;
};

type Event = {
data: UserInterface;
object: "event";
type: EventType;
};

type EventType = "user.created" | "user.updated" | "*";
Sugan_Selvam
Sugan_SelvamOP2y ago
@whatplan (Rustular Devrel) this works locally ?? cos i used the code but somehow it not running
James Perkins
James Perkins2y ago
I don't have an example in the current docs that is super in depth but this new doc here: https://beta-docs.clerk.com/users/guides/sync-data-to-your-backend Tells you how to use everything, note that testing locally you need to expose your endpoint outside of your local application as a webhook is a reverse API
Sync data to your backend with webhooks | Clerk
Authentication and User management for the modern web
Sugan_Selvam
Sugan_SelvamOP2y ago
@James Perkins , Thanks. I was missing the ngrok. This docs helped me to fix it.
Ahmed Heikal
Ahmed Heikal11mo ago
Hey everyone, when I sync clerk data to my back end using webhook and do all steps i receive 401 error on vercel Log and this is my link of problem on stackoverflow : https://stackoverflow.com/questions/77768548/401-unauthorised-when-processing-a-clerk-webhook-in-next-js
Stack Overflow
401 Unauthorised when processing a Clerk Webhook in Next.js
when i sync clerk data to my back end using webhook and do all steps i recive 401 error on vercel Log this is app/api/webhook/route.ts /* eslint-disable camelcase */ import { Webhook } from "s...
Want results from more Discord servers?
Add your server