I’m receiving a 401 Unauthorized
when I try to send a webhook to the https://13f1-...-859.ngrok-free.app/api/webhooks/clerk
endpoint.
Issue
I’m receiving a 401 Unauthorized
when sending a webhook to the provided endpoint.
Steps to Reproduce
- Set up the route handler (
app/api/webhooks/clerk/route.ts
):import { db } from "@/db/db"; import { playlists, users } from "@/db/schema"; import type { User } from "@clerk/nextjs/api"; import { headers } from "next/headers"; import { Webhook } from "svix"; import { eq, isNull, inArray } from "drizzle-orm"; type UnwantedKeys = "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 = process.env.WEBHOOK_SECRET || ""; export async function POST(req: Request) { const payload = await req.json() const payloadString = JSON.stringify(payload); const headerPayload = headers(); const svixId = headerPayload.get("svix-id"); const svixIdTimeStamp = headerPayload.get("svix-timestamp"); const svixSignature = headerPayload.get("svix-signature"); if (!svixId || !svixIdTimeStamp || !svixSignature) { console.log("svixId", svixId) console.log("svixIdTimeStamp", svixIdTimeStamp) console.log("svixSignature", svixSignature) return new Response("Error occured", { status: 400, }) } const svixHeaders = { "svix-id": svixId, "svix-timestamp": svixIdTimeStamp, "svix-signature": svixSignature, }; const wh = new Webhook(webhookSecret); let evt: Event | null = null; try { evt = wh.verify(payloadString, svixHeaders) as Event; } catch (_) { console.log("error") return new Response("Error occured", { status: 400, }) } // Handle the webhook const eventType: EventType = evt.type; const { id, first_name, last_name, emailAddresses } = evt.data; if (eventType === "user.created") { const email = emailAddresses[0].emailAddress; try { await db.insert(users).values({ id, first_name, last_name, email, }); return new Response("OK", { status: 200 }); } catch (error) { console.log(error); return new Response("Error handling user creation in the database", { status: 400, }) } } else if (eventType == "user.deleted") { try { await db.delete(users).where(eq(users.id, id)); const recordsToDelete = (await db.select().from(playlists).leftJoin(users, eq(playlists.user_id, users.id)).where(isNull(users.id))); const idsToDelete = recordsToDelete.map(record => record.playlists.id); await db .delete(playlists).where(inArray(playlists.id, idsToDelete)); return new Response("OK", { status: 200 }); } catch (error) { console.error(error); throw new Error(`Failed to insert user into database`); } } else { console.log("eventType", eventType) return new Response("Invalid event type", { status: 201, }) } } type Event = { data: UserInterface; object: "event"; type: EventType; }; type EventType = "user.created" | "user.deleted" | "*";
- Set up the localtunnel or ngrok tunnel for webhook testing
- Set the
WEBHOOK_SECRET
environment variable - Send a webhook to the
https://13f1-...-859.ngrok-free.app/api/webhooks/clerk
endpoint
Expected Behavior
The webhook should be processed successfully.
Actual Behavior
A 401 Unauthorized
error is returned.