So now that we can successfully fire up a checkout page from Stripe, we need to set up our webhook. So webhooks work in a specific way. They are called to your backend on an unspecific time when a specific action is fulfilled from another service. Like payments or for example synchronizing clerk users with your own database or stuff like that. So here's what we have to do.
When using convex there is a module called HTTP which you can create inside of the convex folder here. So go ahead and create HTTP.ts and in here import HTTP router from convex server and go ahead and import HTTP action from generated server. Create a new HTTP router using HTTP router and then you can do HTTP.route and pass in the path to be slash test method to be get and handler to be an HTTP action, which is gonna be an asynchronous, whoops, let me just write asynchronous outside of the parentheses. So an asynchronous arrow function, which has the context inside of its params. And let's just return a new response, hello world.
And don't forget to do export default HTTP. So how do you access this? Well you can do that by finding out your current development URL. So if you go inside of your environment local in here you have your next public convex URL and you can find the same one inside of your convex right here so inside of the project settings here Just make sure that you are looking at this in development. So let me go into settings here.
We have URL and deploy key. So let's go ahead and see this. So this is the deployment URL for my development instance. So make sure you're doing this on development, right? So you can try and copy this and you can go inside of a new browser and that's gonna tell you page not found.
But if you go ahead and change this from .convex.cloud to .convex.site and go to slash test for example that will give you hello world back and let me just confirm if I go to .cloud yeah .cloud is 404 So you have to change to .site. There is official documentation about this. I'm just not sure where I found it. But yes, basically your development site can be found by changing from .cloud to .site in the URL. And you can see that it found that slash test route which we have just defined inside of our HTTP right here.
So if this is important that is working for you because you need to figure out what is your URL for where the webhook is going to be received in the development mode. So now that you know that, let's go ahead and actually set up our webhook. So in the developers tab on Stripe, go into webhooks right here and click on test in a local environment. So you need to have the Stripe CLI. So you can click here and you can find ways to install Stripe CLI.
If you've already used Stripe in my previous tutorials, then you probably have it. You can test it out by writing Stripe in your command line interface. So in here you have brew instructions, Windows, Docker, Linux, MacOS, whatever you want. And once you have that, you have to run the command stripe login. So let me go ahead inside of my terminal here and run that command.
So I'm gonna go ahead and open a new terminal, stripe login and that will give me this URL which you then have to paste inside of your browser and it's going to ask you whether you want to confirm your identity. So this code here is going to match what you're seeing in your terminal meaning that it's you who is requesting this access. So you can safely press allow access and that's it. Access is now granted and then this step should become completed and it should tell you that you're logged in on your local right here. And now you have to forward your webhook to that URL which we tested out right here.
So to save you some time so you don't have to always write this you can just copy this now and instead you can go inside of your package.json right here and add a new script here. Let's call this Stripe Listen, and you can paste that here. And we are going to modify it to not listen to localhost 4242, but instead it's going to listen to your URL right here. So go ahead and paste that here, like this. Let me zoom out so you can see it in one line and it's gonna go to slash stripe so that we're gonna create that, it doesn't exist yet but it's gonna go to your site slash stripe The same way we added slash test, we are now going to add slash stripe.
So let's go ahead and do that really quickly. Inside of convex here, go inside of HTTP and we are now going to modify this route to instead go to slash stripe. It's going to be a post route and besides context we're also gonna get the request from here and for now we can just do the following We can do a response null and pass in the status 200 for now. So we're just going to make it like this for now. So now your slash test in here should be no matching routes found and then if you go ahead inside of your terminal here you should be able to run npm run stripe listen which you have just created inside of your package.json right here.
And now you're going to get your Stripe API, Stripe Webhook signing secret. So that is quite important. But don't worry, we're going to run this again. I just want to confirm now that we have this. So now let's go ahead and add another script inside of our package.json here which is going to be this.
You can see this is completed right so we successfully run this now and now we can copy the third one here and you can also add that inside of your package JSON here under stripe trigger like this You can just copy it plainly and then go inside of your terminal here open a new one and simply run npm run stripe trigger let me just check. Did I do something wrong? Ambient light, stripe. Oh, stripe. I have a misspell.
Stripe. Alright, and there we go. You can see now if I go back inside of my other terminal here where I'm running my Webhook I get back a 200 post event So make sure that you run this stripe trigger event and then in the terminal where you are running your Stripe Listened event, you need to get 200 back. If it's anything other than 200, something is wrong. So make sure that you're using the correct stripe endpoint here and make sure that you have this set up correctly the same way we just tested this out for the slash test route.
And then all of this should be completed, completed, completed, meaning that you have successfully set this up. Great! So now let's go back and let's actually store our Stripe webhook secret. So let's go ahead and do the following. I want to go inside of my .environment.local here and prepare stripe webhook secret file.
And then you can go inside of your terminal here and let me do you know what, I will shut down all of my terminals because I have too many of them running and we don't need all of them at the moment. So go ahead and do npm run stripe listen and that will generate your webhook signing secret. So just copy the entire bold key like this and you can paste it here and then you have to go inside of your development mode environment variables where you've added next public app URL and Stripe API key and add Stripe webhook secret and paste that here as well and click save. And just confirm that you didn't misspell anything. Stripe webhook secret, Stripe webhook secret.
Like that. And no, you don't have to change it every time you run npm run stripe listen. So I think this only changes when you do a new stripe login, but the stripe listen itself is the same. And it probably also changes if you completely change the URL inside of your forward to. Great!
So now that you have that here's what we have to do next. We have to go inside of our convex stripe method and we have to create an internal method, internal action called fulfill. So let's go ahead and import internal action right here. And let's go to the bottom and let's write export const fulfill to be an internal action which accepts the following arguments signature which is string and payload which is a string. And then we have a handler, which is an asynchronous method, which has the context and we can immediately destructure the signature and the payload from the arguments above.
So first things first, let's get our webhook secret. That's gonna be process.environment.stripe underscore webhook underscore secret. And let's write as string here so we have types for it. Double check that this is exactly what you have inside of your .environment.local So stripe underscore webhook underscore secret. You can see that I think that I had a little typo here or maybe I didn't.
But I often write the strip instead of stripe. So double check that you don't have that typo and more importantly, double check it here because this is where the values is actually gonna be loaded. Okay, and now that we have that, we can continue and create our fulfill method here. So let's open a try block here and in the catch block let's resolve catch so we don't have an error. So you can console error the error and you can simply return success false here.
And in the try block do the following. We need to get an event deconstructed. So stripe.webhooks.constructEvent is going to accept the payload, the signature and our webhook secret. Like that. So how does this work?
Well As I've just mentioned, webhooks work in a way that your backend will receive them in a random time without any idea of what user actually triggered that webhook or what action triggered it. So we can't just allow anything to come to our backend. But at the same time, we need to authorize Stripe and other similar services. So we do that using webhook secrets and constructing events. So using the payload, the signature and the combination of our private webhook secret, we are going to know for certain that whoever is trying to access our back-end using this method is fully approved to do so.
So that's why we need all of this information. And once we get our event approved, which by the way if we don't, is simply going to go inside of the catch method here. Also if you're confused where we get the stripe from, it's because we defined it right here above, in the beginning. So now let's go ahead and do the following. Const session is event.data.object as stripe.checkout.session So make sure you have Stripe imported from Stripe.
Did I change something? I did. And now inside of here what we can do is we can check if event.type is equal to checkout.session.completed. And in here then we can write a console.log and write checkout.completed and let's do a return success true like this. So this is going to be temporary for now because we just want to confirm that we can get this method right here.
We are not even using this session for now. So now that we have this internal mutation here we can do and let's also yeah, let's return this here not inside of the if clause so we will always return success. Because if you return too many errors in Stripe webhook they will shut down that webhook so you need to be scarce with the errors only throw them when something is actually going wrong. Alright so now let's try and test this out So this should log once the user successfully purchases something. This is the event which will be fired.
But before we can test this out we need to actually use this fulfill internal action and we use that inside of our HTTP router right here. So head back inside of your HTTP router and we have to now generate that signature. So let's write const signature to be a type of string which is going to be request.headers.get stripe-signature as string. And it's all supposed to be in one line like this. And then let's go ahead and write const result to be await context dot run action.
And then passing internal, which you can now get from .generated API. Let me just manually import that. Internal from .generated API. And you can pass in internal.stripe.fulfill like this. And that fulfill action accepts this signature and the payload which is going to be await request.text.
And then we're going to do the following. If we got back result.success, we are going to return new response null and pass in the status of 200 else we are going to go ahead and throw the opposite of that so we're going to pass 400 and we are also gonna say webhook error like that so this result will run our internal fulfill action which we've just created which for now is most definitely going to return success true unless this event construction fails. So let's go ahead and try this out now. Make sure that you have your npm run stripe listened running and now let's do npx convex dev and let's also do npm run dev so you should have three terminals opened and make sure you have all of your environment variables set here Let me refresh the code here and let me prepare my logs here in convex. So in here I want to search for fulfill or can I be more specific Can I deselect all and then search for Stripe fulfill?
Okay. Let me refresh this to ensure that this is working. I will now upgrade here and you can also keep your Stripe webhook here open so that you can see if this will hit the endpoint and type in this credit card so 42424242424242 that is the Stripe test credit card which will always succeed. Stripe has a lot of test credit cards, which you can test out different forms. For example, successful payment, failed payment, frozen account and stuff like that.
This one specifically is for a successful checkout and anything else doesn't matter so everything else is just fake. So let's click pay and subscribe and let's see if this will actually hit our endpoint and there we go you can see how I hit a bunch of endpoints you have You can see how I have a bunch of 200 events here. And let's go ahead here and there we go. We can see the log which successfully says checkout completed. Great, so our methods are officially connected.
And now it's pretty logical what we have to do. If we get checkout session completed we have to go ahead and create our organization subscription and then we have to handle one additional event which is to upgrade the current subscription. Great! So I'm gonna end the module here because I want you to be 100% certain that this is working so far and only then go ahead and go into the next module where we are actually going to use these events to create stuff inside of our database. Great, great job!