image

Syncing Clerk into your Database using Webhook in NextJs 14

by:
user avatar

SpiralSrc

Oct 29, 2024
178
0

In some cases, you might need or want to sync your Clerk authentication into your database. With Clerk Webhook, it allows you to sync users data into your database by listening to the e.g "user.create" or "user.update" events by creating a webhook endpoint in the Clerk Dashboard, creating a webhook handler in your Next.js application, and testing the webhook locally using ngrok and the Clerk Dashboard.

Steps below is how you are gonna achieve it:

1. Set up ngrok

In order for the webhook to work, your app has to be on the internet. During development, you will need to expose your local server to the internet to test the webhook locally and in this guide, we will use ngrok which creates a forwarding URL that you can send your webhook payload to and it will forward the payload to your local server.

  • Go to ngrok website to create an account.
  • In your system terminal (command prompt/zsh shell), install ngrok by following their installation/download instructions based for whatever system you are using.
  • After you successfully installed ngrok, run the following command below to add the authtoken given to you to the default ngrok.yml:
1ngrok config add-authtoken <YOUR-TOKEN>
  • Put your app online by running the command below, which then gives you a URL that you then needs to be copy/pasted back to your Clerk dashboard:
1ngrok http http://localhost:3000
This is how it's gonna show in your terminal:
1ngrok (Ctrl+C to quit) 2 3Session Status online 4Account inconshreveable (Plan: Free) 5Version 3.0.0 6Region United States (us) 7Latency 78ms 8Web Interface http://127.0.0.1:4040 9Forwarding https://84c5df474.ngrok-free.dev -> http://localhost:3000 10 11Connections ttl opn rt1 rt5 p50 p90 12 0 0 0.00 0.00 0.00 0.00

*Please note that from your ngrok dashboard, when choosing to run the "Ephemeral Domain", the forwarding url generated can only be used until you quit the connection and will generate you a new url when you run the command again whenever you need to sync your clerk and database. In this case, you will need to change the url in your clerk dashboard webhook endpoint with the new url.

If that is something that you don't want to do, you can click on the "Static Domain" next to the "Ephemeral Domain" tab and run the command given with the static domain generated to you. That way, you will only need to run the command with your static domain in your terminal for your clerk to be connected to your app's database whenever you needed without having to copy/paste a new generated url again into your clerk dashboard.

2. In Your Clerk Dashboard

After installing ngrok and was able to generate a forwarding URL, go back to your Clerk Dashboard and follow steps below:

  1. Add an endpoint - Navigate into your configure tab and on the left side bar, select "Webhooks" option and click "Add Endpoint".

  2. Copy/paste the forwarding url generated to you by ngrok, select the events you needed to be synced into your database and click on "Create".

  3. Add your app's webhook endpoint after the url:

1https://84c5df474.ngrok-free.dev/api/webhooks/clerk

3. Adding "Signing Secret" to your .env/.env.local

From your Clerk Dashboard, after adding an endpoint, you will see the "Signing Secret" key on the right side of the page. Click on the eye icon to reveal the key, copy and then paste it to your env file.

1NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=<your-public-key> 2CLERK_SECRET_KEY=<your-clerk-secret-key> 3WEBHOOK_SECRET=<signing-secret-key>

4. Create the Webhook endpoint in your NextJs Application

Finally, the last thing you need to do in order to get your Clerk running in sync to your app's database is to create an endpoint in your nextjs app. In your app, your file is gonna look like "/app/api/webhooks/clerk/route.ts".

*Make sure to go back to your Clerk Dashboard webhook endpoint to add the "/api/webhooks/clerk" at the end of the url.

  • Install svix

First thing you need to do is to install svix which is gonna be used to verify the webhook signature and that receives the webhook's payload. Install it by running the following command in your terminal:

1npm install svix
  • Endpoint below, provided by Clerk in their documentation (reference link will be linked at the end of the post), only showing the log of the payload to the console which will also going to be the guide or basis you can use when you add the actions/events you need to add.
1import { Webhook } from 'svix' 2import { headers } from 'next/headers' 3import { WebhookEvent } from '@clerk/nextjs/server' 4 5export async function POST(req: Request) { 6 // You can find this in the Clerk Dashboard -> Webhooks -> choose the endpoint 7 const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET 8 9 if (!WEBHOOK_SECRET) { 10 throw new Error('Please add WEBHOOK_SECRET from Clerk Dashboard to .env or .env.local') 11 } 12 13 // Get the headers 14 const headerPayload = headers() 15 const svix_id = headerPayload.get('svix-id') 16 const svix_timestamp = headerPayload.get('svix-timestamp') 17 const svix_signature = headerPayload.get('svix-signature') 18 19 // If there are no headers, error out 20 if (!svix_id || !svix_timestamp || !svix_signature) { 21 return new Response('Error occured -- no svix headers', { 22 status: 400, 23 }) 24 } 25 26 // Get the body 27 const payload = await req.json() 28 const body = JSON.stringify(payload) 29 30 // Create a new Svix instance with your secret. 31 const wh = new Webhook(WEBHOOK_SECRET) 32 33 let evt: WebhookEvent 34 35 // Verify the payload with the headers 36 try { 37 evt = wh.verify(body, { 38 'svix-id': svix_id, 39 'svix-timestamp': svix_timestamp, 40 'svix-signature': svix_signature, 41 }) as WebhookEvent 42 } catch (err) { 43 console.error('Error verifying webhook:', err) 44 return new Response('Error occured', { 45 status: 400, 46 }) 47 } 48 49 // Do something with the payload 50 // For this guide, you simply log the payload to the console 51 const { id } = evt.data 52 const eventType = evt.type 53 console.log(`Webhook with and ID of ${id} and type of ${eventType}`) 54 console.log('Webhook body:', body) 55 56//Events/Actions here 57 58 return new Response('', { status: 200 }) 59} 60
  • Sample event/action I made for my apps inserted just before the response status of 200 from the above code snippet:
1// checking the event type whether to create the user if email address does not exist on the database, 2// or update if user exist 3// (you can also choose id or any other unique identifier) 4 5if (eventType === "user.created" || eventType === "user.updated") { 6 try { 7 const emailAddress = 8 evt.data.email_addresses?.[0]?.email_address || null; 9 10 if (!emailAddress) { 11 throw new Error("Email address not found"); 12 } 13 14 //Use a transaction to ensure atomicity 15 await prisma.$transaction(async (tx) => { 16 const existingUser = await tx.user.findUnique({ 17 where: { 18 email: emailAddress, 19 }, 20 }); 21 22 if (existingUser) { 23 // Update the existing user 24 await tx.user.update({ 25 where: { 26 id: existingUser.id, 27 }, 28 data: { 29 // username: JSON.parse(body).data.username, 30 firstName: evt.data.first_name, 31 lastName: evt.data.last_name, 32 avatar: JSON.parse(body).data.image_url, 33 }, 34 }); 35 console.log("User updated successfully"); 36 } else { 37 // Create a new user 38 await tx.user.create({ 39 data: { 40 // id: evt.data.id, // Use string ID 41 clerkId: evt.data.id, //clerk id 42 email: emailAddress, 43 // username: evt.data.username, 44 firstName: evt.data.first_name, 45 lastName: evt.data.last_name, 46 avatar: evt.data.image_url, 47 }, 48 }); 49 console.log("User created successfully"); 50 } 51 }); 52 53 return new Response("User has been processed successfully!", { 54 status: 200, 55 }); 56 } catch (error) { 57 console.log(error); 58 return new Response("Failed to process user!", { status: 500 }); 59 } 60 } 61

Conclusion

By implementing these steps, you’ll enable efficient real-time syncing between Clerk and your application’s database, ensuring that user data remains accurate and up-to-date with minimal manual intervention. This approach optimizes your app's user management process and provides a strong foundation for scaling your authentication workflows effectively.

Reference:

Clerk's Intergrations, check out the Sync Clerk data to your application with webhooks.

Write a Comment

*Please log in or sign up to post a comment
or