Amrit Chattopadhyay
Amrit Chattopadhyay

Reputation: 21

Clerk Webhook message is failing

I am using next js with clerk to authenticate user. I am using clerk webhook but the messages are failing. I deployed my app to vercel and used the url of the deployed app as an endpoint in clerk webhook endpoint. Pleaes help.

route.js

import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { WebhookEvent } from '@clerk/nextjs/server';
import { createUser, deleteUser, updateUser } from '@/lib/actions/user.actions';
import { clerkClient } from '@clerk/nextjs';
import { NextResponse } from 'next/server';

export async function POST(req: Request) {

  // You can find this in the Clerk Dashboard -> Webhooks -> choose the webhook
  const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

  if (!WEBHOOK_SECRET) {
    throw new Error('Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local');
  }

  // Get the headers
  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 there are no headers, error out
  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);

  // Create a new Svix instance with your secret.
  const wh = new Webhook(WEBHOOK_SECRET);

  let evt: WebhookEvent;

  // Verify the payload with the headers
  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 occurred', {
      status: 400
    });
  }

  // Get the ID and type
  const { id } = evt.data;
  const eventType = evt.type;

  if (eventType === 'user.created') {
    const { id, email_addresses, image_url, first_name, last_name, username } = evt.data;

    const user = {
      clerkId: id,
      email: email_addresses[0].email_address,
      username: username!,
      firstName: first_name,
      lastName: last_name,
      photo: image_url,
    };

    const newUser = await createUser(user);

    if (newUser) {
      await clerkClient.users.updateUserMetadata(id, {
        publicMetadata: {
          userId: newUser._id
        }
      });
    }

    return NextResponse.json({ message: 'OK', user: newUser });
  }

  if (eventType === 'user.updated') {
    const { id, image_url, first_name, last_name, username } = evt.data;

    const user = {
      firstName: first_name,
      lastName: last_name,
      username: username!,
      photo: image_url,
    };

    const updatedUser = await updateUser(id, user);

    return NextResponse.json({ message: 'OK', user: updatedUser });
  }

  if (eventType === 'user.deleted') {
    const { id } = evt.data;

    const deletedUser = await deleteUser(id!);

    return NextResponse.json({ message: 'OK', user: deletedUser });
  }

  return new Response('', { status: 200 });
}

middleware.js

import { authMiddleware } from "@clerk/nextjs";
 
export default authMiddleware({
  publicRoutes: [
    '/',
    '/events/:id',
    '/api/webhook',
    '/api/webhook/stripe',
    '/api/uploadthing'
  ],
  ignoredRoutes: [
    '/api/webhook/clerk',
    '/api/webhook/stripe',
    '/api/uploadthing'
  ]
});
 
export const config = {
  matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};
 

folder structure clerk dashboard

I want to save the data of the authenticated users in my mongodb database.

Upvotes: 2

Views: 1398

Answers (2)

Third App
Third App

Reputation: 1

    import { Webhook } from "svix";
    import { headers } from "next/headers";
    import { WebhookEvent } from "@clerk/nextjs/server";
    import { db } from "@/lib/db";
    
    export async function POST(req: Request) {
      // You can find this in the Clerk Dashboard -> Webhooks -> choose the endpoint
      const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
    
      if (!WEBHOOK_SECRET) {
        throw new Error(
          "Please add CLERK_WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local"
        );
      }
    
      // Get the headers
      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 there are no headers, error out
      if (!svix_id || !svix_timestamp || !svix_signature) {
        return new Response("Error occured -- no svix headers", {
          status: 400,
        });
      }
    
      // Get the body
      const payload = await req.json();
      const body = JSON.stringify(payload);
    
      // Create a new Svix instance with your secret.
      const wh = new Webhook(WEBHOOK_SECRET);
    
      let evt: WebhookEvent;
    
      // Verify the payload with the headers
      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,
        });
      }
    
      // Do something with the payload
      // For this guide, you simply log the payload to the console
    
      const eventType = evt.type;
    
      if (eventType === "user.created") {
        await db.user.create({
          data: {
            externalUserId: payload.data.id,
            imageUrl: payload.data.image_url,
            username: payload.data.username,
          },
        });
        console.log('eventType',eventType)
    
      }
    
      if (eventType === "user.updated") {
        await db.user.update({
          where: {
            externalUserId: payload.data.id,
          },
          data: {
            username: payload.data.username,
            imageUrl: payload.data.image_url,
          },
        });
      }
    
      if (eventType === "user.deleted") {
        await db.user.delete({
          where: {
            externalUserId: payload.data.id,
          },
        });
      }
    
      return new Response("Success", { status: 200 });
    }


auth cleark Middleware----------------------



import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher([
'/',
  '/api/webhooks(.*)'
])

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect()
})

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],

}

Upvotes: 0

Nebiyu
Nebiyu

Reputation: 11

In the Clerk documentation route.ts file should be on api/webhooks folder and update ur endpoints on clerk dashbord accourding to that and go to clerk dashboard, user&authentication, email,phone, username then click on the setting icon beside Username and allowed the username required, then I did the same for the Personal information allow the first name and last name to be required, I guess by doing this the setting in clerk dashboard is aligned with the user model created where these info are required for the user object to be created inside the database collection and last thing make sure 'publicRoutes' inside the middleware.ts file contains this line "/api/webhooks(.*)", this will fix it

Upvotes: 1

Related Questions