Syncing Clerk User for Prisma User in the Next Pages Directory

Hey there, I am trying to sync my user object with my User model in Prisma DB. I followed the steps in this video: https://www.youtube.com/watch?v=NgBxrIC1eHM&t=1293s But then I realized that the headers only work with server components in the app directory. I am using t3 stack so I have tRPC and the server folder. How can I go about converting this code I have currently to a tRPC call, or something else entirely since it has to be a endpoint for webhooks.
import { headers } from "next/headers";
// svix is used to verify the webhook signature from Clerk
import { Webhook, WebhookRequiredHeaders } from "svix";
import { Water_Brush } from "next/font/google";
import { IncomingHttpHeaders } from "http";
import { NextResponse } from "next/server";
import { prisma } from "~/server/db";

const webhookSecret = process.env.CLERK_WEBHOOK_SECRET || "";

async function handler(request: Request) {
const payload = await request.json();
const headersList = headers();
const heads = {
"svix-id": headersList.get("svix-id"),
"svix-timestamp": headersList.get("svix-timestamp"),
"svix-signature": headersList.get("svix-signature"),
};

const wh = new Webhook(webhookSecret);

let evt: Event | null = null;
console.log("payload", payload);
try {
evt = wh.verify(
JSON.stringify(payload),
heads as IncomingHttpHeaders & WebhookRequiredHeaders
) as Event;
} catch (err) {
console.log((err as Error).message);
return NextResponse.json({}, { status: 400 });
}

const eventType: EventType = evt.type;

if (eventType === "user.created" || eventType === "user.updated") {
const { id, ...attributes } = evt.data;
console.log(id, "id in clerk-webhook-handlers");
console.log(attributes);
// When you are ready to move the attributes property in the Clerk user from JSON in the Prisma DB to property values of the User object in Prisma DB destructure the attributes

await prisma.user.upsert({
where: { externalId: id as string },
create: {
externalId: id as string,
attributes,
},
update: {
attributes,
},
});

// const attributes = {
// firstName: "",
// lastName: ""
// }
}
// upsert = if the user exists update the user, if the user doesn't exist create the user
}

type EventType = "user.updated" | "user.created" | "*";

type Event = {
data: Record<string, string | number>;
object: "event";
type: EventType;
};

export const GET = handler;
export const POST = handler;
export const PUT = handler;

// src/pages/api/clerk-webhook.ts
import { headers } from "next/headers";
// svix is used to verify the webhook signature from Clerk
import { Webhook, WebhookRequiredHeaders } from "svix";
import { Water_Brush } from "next/font/google";
import { IncomingHttpHeaders } from "http";
import { NextResponse } from "next/server";
import { prisma } from "~/server/db";

const webhookSecret = process.env.CLERK_WEBHOOK_SECRET || "";

async function handler(request: Request) {
const payload = await request.json();
const headersList = headers();
const heads = {
"svix-id": headersList.get("svix-id"),
"svix-timestamp": headersList.get("svix-timestamp"),
"svix-signature": headersList.get("svix-signature"),
};

const wh = new Webhook(webhookSecret);

let evt: Event | null = null;
console.log("payload", payload);
try {
evt = wh.verify(
JSON.stringify(payload),
heads as IncomingHttpHeaders & WebhookRequiredHeaders
) as Event;
} catch (err) {
console.log((err as Error).message);
return NextResponse.json({}, { status: 400 });
}

const eventType: EventType = evt.type;

if (eventType === "user.created" || eventType === "user.updated") {
const { id, ...attributes } = evt.data;
console.log(id, "id in clerk-webhook-handlers");
console.log(attributes);
// When you are ready to move the attributes property in the Clerk user from JSON in the Prisma DB to property values of the User object in Prisma DB destructure the attributes

await prisma.user.upsert({
where: { externalId: id as string },
create: {
externalId: id as string,
attributes,
},
update: {
attributes,
},
});

// const attributes = {
// firstName: "",
// lastName: ""
// }
}
// upsert = if the user exists update the user, if the user doesn't exist create the user
}

type EventType = "user.updated" | "user.created" | "*";

type Event = {
data: Record<string, string | number>;
object: "event";
type: EventType;
};

export const GET = handler;
export const POST = handler;
export const PUT = handler;

// src/pages/api/clerk-webhook.ts
LiveCode247
YouTube
How to sync Clerk authenticated users with your own database in Nex...
In this video, we'll be looking at how to sync your Clerk authenticated users with your own database. Clerk | Sync data to your backend: https://clerk.com/docs/users/sync-data-to-your-backend Clerk Example using NextJS (Pre v13): https://github.com/clerkinc/clerk-nextjs-examples/blob/main/examples/widget/pages/api/webhooks/user.ts Svix webhook ...
2 Replies
deme4447
deme444712mo ago
Figured it out, just put this in the pages/api/path:
import type { IncomingHttpHeaders } from "http";
import type { NextApiRequest, NextApiResponse } from "next";
import type { WebhookRequiredHeaders } from "svix";
import type { WebhookEvent } from "@clerk/nextjs/server";
import { Webhook } from "svix";
import { prisma } from "~/server/db";
import { NextResponse } from "next/server";

const webhookSecret: string = process.env.CLERK_WEBHOOK_SECRET!;

export default async function handler(
req: NextApiRequestWithSvixRequiredHeaders,
res: NextApiResponse
) {
const payload = req.body as UserData;
console.log("payload", payload);
const headers = req.headers;
console.log("headers", headers);
// Create a new Webhook instance with your webhook secret
const wh = new Webhook(webhookSecret);

let evt: WebhookEvent;

try {
// Verify the webhook payload and headers
evt = wh.verify(JSON.stringify(payload), headers) as WebhookEvent;
console.log("evt", evt);
} catch (err) {
// If the verification fails, return a 400 error
console.error(err as Error);
// return NextResponse.json({}, { status: 400 });
return NextResponse.json({}, { status: 400 });
}

const eventType = evt.type;

if (
eventType === "user.created" ||
eventType === "user.updated" ||
eventType === "user.deleted"
) {
const { id, ...attributes } = evt.data;

console.log(id, "id in clerk-webhook-handlers");
console.log(attributes, "attributes in clerk-webhook-handlers");

// map the return data in to the format we want to store in our database
// const userDataUpsert = {
// externalId: id as string,
// attributes: attributes as Record<string, any>,
// };

await prisma.user.upsert({
where: { externalId: id as string },
create: {
externalId: id as string,
attributes: attributes as Record<string, any>,
},
update: {
attributes: attributes as Record<string, any>,
},
});
}

// return 200 to acknowledge receipt of the event
res.status(201).json({});
}

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

// type EventType = "user.updated" | "user.created" | "user.deleted" | "*";
import type { IncomingHttpHeaders } from "http";
import type { NextApiRequest, NextApiResponse } from "next";
import type { WebhookRequiredHeaders } from "svix";
import type { WebhookEvent } from "@clerk/nextjs/server";
import { Webhook } from "svix";
import { prisma } from "~/server/db";
import { NextResponse } from "next/server";

const webhookSecret: string = process.env.CLERK_WEBHOOK_SECRET!;

export default async function handler(
req: NextApiRequestWithSvixRequiredHeaders,
res: NextApiResponse
) {
const payload = req.body as UserData;
console.log("payload", payload);
const headers = req.headers;
console.log("headers", headers);
// Create a new Webhook instance with your webhook secret
const wh = new Webhook(webhookSecret);

let evt: WebhookEvent;

try {
// Verify the webhook payload and headers
evt = wh.verify(JSON.stringify(payload), headers) as WebhookEvent;
console.log("evt", evt);
} catch (err) {
// If the verification fails, return a 400 error
console.error(err as Error);
// return NextResponse.json({}, { status: 400 });
return NextResponse.json({}, { status: 400 });
}

const eventType = evt.type;

if (
eventType === "user.created" ||
eventType === "user.updated" ||
eventType === "user.deleted"
) {
const { id, ...attributes } = evt.data;

console.log(id, "id in clerk-webhook-handlers");
console.log(attributes, "attributes in clerk-webhook-handlers");

// map the return data in to the format we want to store in our database
// const userDataUpsert = {
// externalId: id as string,
// attributes: attributes as Record<string, any>,
// };

await prisma.user.upsert({
where: { externalId: id as string },
create: {
externalId: id as string,
attributes: attributes as Record<string, any>,
},
update: {
attributes: attributes as Record<string, any>,
},
});
}

// return 200 to acknowledge receipt of the event
res.status(201).json({});
}

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

// type EventType = "user.updated" | "user.created" | "user.deleted" | "*";
If anyone sees this: you'll sync the full attributes object in one prisma column unless you make your prisma model individual properties for each value in the object which you have to destructure before upserting to prisma
Quatre
Quatre11mo ago
will this work with t3-turbo on expo?