So let's go ahead one more time and just establish our knowledge of local tunnels. So I'm gonna shut down all of my terminals. So I will shut down this and I will shut down this. I only have one terminal running and I cannot connect to my local host. So whenever you're starting your project in the future, go ahead and do npm run dev.
That's the first step you have to do and then in another terminal go ahead and use that ngrok command. You can see I have it right here. If you don't have it you can find it, you can just go back in the tutorial where I showed you exactly where it's written in ngrok.com website and make sure that when you copy that you change the port to be 3000 and not 80. So now my website is available both on localhost 3000 here but it's also available on this URL right here. So if I go back here and change this to my URL I'm seeing exactly what I just saw on my local host.
Perfect! So what we're ready to do now is create our Clerk webhook. So for that I'm going to use their documentation here. So what they say to do first is enable webhooks. So go to your clerk dashboard here and find the webhooks in the sidebar.
And inside of here go ahead and click add endpoint. And in here you're gonna paste not your localhost but that ngrok URL which we just established which you can get from your terminal right here so this HTTPS option right here. And what we're gonna do is we're gonna change this endpoint to go to slash API slash webhooks slash clerk. So this is very important this is our structure make sure it goes to slash api slash webhooks so multiple because we're going to have more of them and specifically the clerk one. Great And then in the message filtering go ahead and select user and go ahead and select everything so user created, deleted and updated and click create.
Perfect! So confirm one more time that this URL is correct you can also copy it from here and paste it in your tab one more time and your local host should be showing up here. Great! So what we have to do now is actually build the webhook. So we just enabled everything we need here.
What we need to do now is get the signing secret. So inside of this dashboard here, where you added this endpoint, you should see the signing secret. So go ahead and click on the little eye icon here, and that's gonna allow you to see this secret. And the docs then say to store that inside of webhook secret inside our environment file but we're not going to store it as that we're going to store it as something else so let me zoom back in here and go inside of my .environment file and in here let's go just below this next public clerk sign up URL and in here I'm gonna go ahead and add clerk underscore webhook underscore secret. So the reason I'm adding a prefix clerk is because we're gonna have multiple webhooks here.
So make sure you paste your super secret signing URL here, which you can get from your dashboard right here. So sign in secret, we clicked on the little eye icon and we got the sign in secret. Alright, so now that we have that you can go ahead and read even further about understanding the webhook payload so you know how that's gonna look. But what we have to do next is install Svicks. So let's go ahead and do that.
So this is also only needed for development, right? So don't worry about this. So go ahead and install Svicks like this. And I just think I just shut down my ngrok. So make sure you install Svicks.
And if you accidentally shut down your ngrok like I did, just go ahead and rerun that command on your stable domain with port 3000 and ensure that in another terminal you have npm run dev running. So it's important that both of these are running at the same time. Great, so I'm gonna open a third terminal. What we had to do was do npm install suex so just make sure that you do that. Great and now we have instructions on how to create the webhook.
But we're not gonna create it in this exact repository. We're gonna create it in a different one. So let's go ahead and close everything here, go inside of our app folder, and in here create a new folder called API. And then inside of that, another folder, webhooks, and then inside of that another folder webhooks and then inside of that another folder clerk and then create a file a route.ts so app folder API webhooks clerk so exactly what we've given it right here right so let me go ahead and expand this. So our ngrok URL slash API webhooks clerk.
So it needs to be the same. Great, so let me just collapse this, go back here and let's take a look at what we have to add here. So first let's add our imports. So you can go ahead and open this documentation as well. I'm going to paste the link in the description or you can just follow along as I do.
So first we're going to add the webhook from SWX. We're going to import headers from next headers and we're going to import this is a type webhook event from clerk next js server. After that we're going to go ahead and actually enable this route to be a POST function. So let's go ahead and give it an end curly bracket here and then let's go ahead and do the following. Let's see if we have the webhook secret.
So I'm going to copy this from here and paste it inside and let me just zoom out a bit so you can see. So this is what it's supposed to look like but we have to modify it a little bit. So here we look for process.environment.webhooksecret but if you take a look at our .environment we have clerk.webhooksecret so copy this from .environment file and then replace this so no need to replace this constant just replace where the constant gets its information from which is process.environment.clerkWebhookSecret and it would also be a good idea to fix this error which currently tells you that if there is no webhook secret please add that to your environment. So let's be correct and tell it that they need to add clerk webhook secret to our environment file. Perfect!
So make sure you've added that. What we have to do next is get the headers. So let's go ahead and copy this let's go just below this if clause here and let's get our headers so I'm just gonna indent this like that so we get the headers from nextjs and then from that headers object we get the swix id the swix timestamp and the swix signature So we're going to use these three objects, sorry these three options in combination with our Clerk webhook secret to verify that what is trying to access this endpoint has permissions to our database. Otherwise any user would be able to trigger our webhook and that's not good, right? So we need to enable this to happen.
And now let's go ahead and add this if clause that if there are no headers break the function immediately. So we're not going to allow anyone to do this if they don't provide the headers which are SWXID, timestamp and SWXSignature for which we check right here. So if any of them are missing we pass in the error and prevent them from accessing our webhook. Great! And now let's go ahead and let's get the body and the payload.
So after this if clause which checks for sphix headers, let's go ahead and get the body. So we get the payload using await request.json so make sure that this is an asynchronous function if you didn't copy it but instead you wrote it yourself and then we stringify that so we turn that object inside of a string and then what we're gonna do, let me just add it like this is we're gonna initialize our webhook with our webhook secret constant and then we're gonna attempt to verify it so let's do this step by step first I'm just gonna go ahead and create a new SWX instance here So after we get our body let's go ahead and let's add this. So create a new SWX instance. So we create this VH constant using the new webhook which we imported from SWX and we pass in the webhook secret which we get right here using our environment field clerk webhook secret which if you check is a field right here. Great!
And now that we have that let's go ahead and try and verify that. So copy this try and catch block right here and just below this event paste that here. So we open a try and catch block where we use this webhook with our webhook secret and we attempt to verify who this user is who is trying to access our webhook using the SWIX ID from the headers which we have right here, SWIX timestamp and SWIX signature. So all of those things need to match in order for whatever is trying to access our webhook to actually have access. So this is going to be a very secure webhook.
And the reason we need to secure it like this is because we're going to allow this to be a public URL. So anyone will be able to ping this route. But only those with proper headers and which matches in combination with our webhook secret will be able to actually continue and not get this error that there was a problem verifying the webhook. Great! So save this try and catch event and let's see what else we have to do here.
So after that we're practically done. We can now access the data, the event type and just copy these two constants, these two console logs and this response return right here. So outside of this try catch function and the function like this. So we have these two new constants with the ID and the event type. And what's important in webhooks is that you return a response.
In our case our default response is going to be 200 so if you don't return a response I think that then the webhook is going to stay in timeout or pending something like that. So make sure you always add a response at the end. Perfect! So let's go ahead and review this one more time. Inside of your .environment you need to have a Clerk webhook secret so make sure you copied this in full so confirm one more time in your clerk dashboard here.
I'm going to expand my screen. Make sure you have this sign in secret and make sure that you copied it from start to end. Then make sure that you have pasted that here and make sure that clerk webhook secret matches exactly what you check in this constant ClerkWebhookSecret like this. Perfect! And here's you can already try this I believe so let me go ahead and copy this and try that in my browser.
So if I go here, there we go, I have a response null here and I think that's because we got intercepted somewhere. So let's go ahead and try that. Is this correct API webhooks clerk? I think we can try and initiate an event here so let's go into testing so go inside your clerk dashboard into testing here and let's create user.created like this and let's go ahead and prepare our terminal here and go ahead and open the ngrok terminal so I'm gonna zoom out so we can see everything that's going on yes I think I'm not sure if that's gonna appear here or in the ngrok one so make sure you have both of this ready so let's go here select user.created it doesn't matter so just fire any event for now but we do listen only for user updates so make sure you select one of the user ones and go ahead and click send example and let's see if this is working or not. Oh it looks like it's preventing us from connecting to this webhook because we forgot to add it to public routes, right?
So let's go ahead and do the following before we try this again. So go inside of your middleware.ts right here and inside of this object add a public routes with an array and then inside of that you're gonna go ahead and write slash API slash webhooks and we're gonna go ahead and enable this route for all webhooks inside so this is gonna match both a slash clerk and whatever we're gonna have in the future so let's try this again I think that now if I try this here it looks like this page isn't working. Yes, because I get a 405. So that's correct. Yes, so the error is 405 meaning that we are now throwing that error so if we take a look back inside of our app API route I think that we throw do we throw a 405 well basically it's working I don't know what else to say all right and let's try this again So now let's fire that event from the clerk dashboard.
So send example, and there we go. Look at this. We now have, so where is it? It's not in ngrok. It's where your local host npm run dev is running.
This is where it appears. So you should be having this huge log right here, right? And you can see that the event is user.created. So let's simplify this and let's only log the ID with the event type. So I'm gonna go back inside of my terminal here and I'm going to gonna add just a bunch of spaces here like that and I'm gonna zoom out here and I'm gonna send the example again and there we go!
Webhook with ID of some user ID and the type of user created. So just confirm that you're getting this console log right here. So let's recap everything we had to do to get to this. So what's important is that you've enabled your API webhooks to be a public route so anyone can access that whether they are logged in or not. And then what's important is that when you try and go manually on that route, so look at my URL, it's that ngrok URL slash API webhooks clerk when I try like this I get a 405 because I'm not authorized because I don't have the headers right And then make sure that in here you also have the correct URL using that ngrok URL slash API webhooks clerk.
Go into testing here, select a random user event. So let's try with deleted now. Go ahead and prepare your terminal here and go inside of the terminal where you are running npm run dev. So not inside of the ngrok one but the other one and let me add a couple of spaces here so we can see it. I'm gonna send this example here and there we go now there is a type of user.deleted perfect so this is now working and now let's try and shut everything down so I'm shutting down localhost and I'm shutting down ngrok so I'm deleting all of my terminals here right So now my application is not gonna be running I believe.
There we go. And if I try and send an example now I should get an error here. And let's go ahead and refresh. There we go. You can see how it says that there is an error right here.
This one failed because my app is not running. So I want to teach you how to rerun your app because I guess you're going to be doing this over a couple of days and you're probably not going to have this running all the time. So step one npm run dev. Step two open a new terminal and run that ngrok command with the port 3000 and with your stable url if you have that feature and then once you get that you're gonna have this url right here So if you're using ngrok with different domains every time, then you're gonna have to copy the domain from here every time, go inside of your webhook, click on edit, and you're gonna have to paste it here. Just remember to add this webhook API webhooks clerk.
So whenever you do this, make sure that you go into testing. Let's go ahead and try another event. Let's click send example, and let's see if that's gonna work. There we go. So it succeeded the last event right here and I think that we can see that not inside of the ngrok but inside of our first terminal there we go webhook with an id of this user id and type of user created perfect so make sure you have these two things running make sure that you're getting this proper console logs and what we're gonna do now is we're gonna go ahead and actually connect to our Prisma and create a user inside of our database every time our user has logged in using a clerk.
What I want to do first is actually delete all of my users from my clerk dashboard right here. So I'm gonna go inside of my users here I'm gonna find my user and I'm gonna delete this user. So we start from a blank slate, right? Make sure you have no users inside of your clerk dashboard. And then what we're gonna do is just make sure your localhost is running of course, make sure your ngrok is running as well.
What we're gonna do is modify this to create a new user model every time we receive an event of user.created. And we're actually not going to need the ID like this. So you can remove this, just leave the event type and remove these two console logs. Let's go ahead and do the following. If event type is user.created in that case we're gonna have to update our database so let's go ahead and let's import database from add slash lib DB which is this little util which we created which has access to our database.
So now that we did that let's go inside of this if clause and let's write await DB dot user dot create. And let's write data external user ID to be payload.data.id and we have this payload right here so we're not going to be using the body here which is stringified we're going to be using payload directly so make sure you don't accidentally use body so we're passing the external user ID which is the clerk user ID to this field and then we're gonna pass in the username to be payload.data.username then we're going to add image URL to be payload.data.imageurl and actually it's not image URL like this it is image underscore URL so the field is a bit different you can visit the documentation to see how it exactly looks or you can I think you can actually see it inside of your dashboard here inside of webhooks let's go ahead and click here when you click on testing and select user.created in here you can see what you're going to get? So we all of these things and there we go we have image URL so that's how we know that it's with an underscore right it's a bit different than ours.
So we're gonna match the image URL and I think that is it for now. Let's take a look at our Prisma schema. So Prisma schema we have ID which is automatically created, we have username, image URL and external user ID. These three fields are required. This doesn't matter and this doesn't matter for the webhook.
Great, and I think we can already do that. So let's go ahead and try that now. So we can go, you can still work on localhost. So you don't have to use that URL. So just go to your localhost, right?
And what I'm going to do before I try this is also run my Prisma Studio. So I can look at my database in real time. So open a third terminal and run npx prisma studio and that's gonna open it on localhost 5555 and right now I have no users inside of my database. So I'm gonna go ahead and click continue with Google and create my account. Make sure you've also deleted all of your users from Clerk.
And now I'm going to enter my username, code with Antonio, and I'm going to click continue and let's see if this is going to work now. So I'm going to go inside of my Prisma Studio, I'm gonna refresh my users and there we go! We are synchronized! Every time a new user uses Clerk to register on our app, we now have them in our own database, which means that we have their image URL and we have most importantly, the external user ID. So for other webhook events like user updated, this is what we're gonna look for, the external user ID.
And that's how you're gonna update their image or their username, right? And the bio is null, that is correct. And you can, I believe, also take a look at here in the overview, there we go? We have the last event which was fired and it was successful, perfect. So now what I wanna do is an ability to modify my image URL or more specifically my username and I also want that to change inside of my database.
So right now my username is codewithantonio. So let's go ahead and add another type of event here for user updated so let's go ahead and do if event type is user dot updated in that case go ahead and do a wait Actually first let's attempt to find this user. So const currentUser is going to be await db.user find unique where externalUserId is matching payload.data.id. So how do I know this is the field I'm looking for? Because that's what we stored when we created the user.
So now I know, all right, this is how I'm going to find the user that my webhook is trying to do something with. And if there is no current user here, let's go ahead and return new response user not found and let's give it a status of 404 like that. Otherwise if we have the user let's do await db.user.update where external user ID is payload.data.id and let's modify the data for this user so that can either be the username which is going to be payload data username or it can be image URL which is going to be payload.data.image underscore URL so make sure you add the underscore the same thing we did here perfect let's try that out now so we have our event ready make sure it says user updated So my username right here is codewithantonio in the database, but if I go ahead and click on manage my account and click on change username and change this to something else and click continue. Let's go ahead and refresh my Prisma Studio now. And there we go, my username is now something else.
Perfect, exactly what we wanted. And the same thing is working with the image URL but I can't really show that because the URL is just a huge string right but the image URL is now also working so you can also update the image of the user and it's going to be updated everywhere on your website. One last event that we have to take care of is if the user deletes their account. So let's go ahead and outside of here and let's write if event type is user deleted. Let's do await DB user delete where external user ID matches payload data ID.
Like that. Perfect. And I actually believe inside of this user updated, I think we can remove this. I think we can just do this I think it's exactly the same thing alright let's try that out so now once the user has been deleted our database should clear it as well. So in my database I now have one user but if I go to manage account and if I go all the way down and click on delete account and confirm, delete account and click on delete account and go in my Prisma Studio and refresh and it looks like it's still here and you have to refresh two times so the webhooks are not exactly instant but there we go our user has been deleted officially from the database as well Let's go back to localhost 3000 and let's go ahead and create a new account again.
So I'm back and let's write code with Antonio again. So I'm creating a completely new user. Let's go here. Let's refresh. There we go.
The user is back. Let's go ahead and try and change my username. Something else. Let's click continue. Let's refresh.
And there we go. So our webhook is working perfect. We have synchronized our clerk users with our database. That's going to be very helpful for us because we now have them inside of our familiar lib where we can access them directly in the database. We don't even have to use all the clerk features now, which for this project is gonna make some things a bit simpler.
Great, great job.