Verifying Shopify webhooks with NodeJS & Express
We can use webhooks to listen for specific store events. For example, instead of checking for new orders every 5 minutes, a webhook can let us know each time an order is placed. And it’s more than just order creation, there are 50+ events available.
Here’s a quick guide on creating and verifying Shopify webhooks.
Step 1: A basic Express server
Step 2: Creating a tunnel (to test locally)
We’ll need to give Shopify a public URL to send notifications to. If you’re working locally, create a tunnel. In this example I’m using localtunnel
$ npm install -g localtunnel
$ lt --port 3000
-> your url is: https://xxxxxxxxxxx.localtunnel.me
Take note of the URL, we’ll need it for the next step.
Step 3: Create a webhook
In your store’s admin, head to Settings > Notifications and scroll down to webhooks. Add a webhook for ‘order creation’.
Be sure to match the URL from step 2.
Step 4: Test the webhook
With both Localtunnel and the Express server running: cross your fingers, hit the “Send test notification” button, and watch the server logs:
Step 5: Verify the webhook
Right now anyone can send a request to your server. This is dangerous. Let’s verify the request actually comes from Shopify.
Grab your secret key from the admin (under the webhooks, highlighted) and update line 5 of the code below.
Also note, you’ll need to install the npm package raw-body
$ npm install raw-body
Here’s the updated code:
I wanted to keep this guide really simple and focus on the verification steps. Some other things to consider:
- Don’t forget to store your secret key in an environment variable.
- To access the webhook data (like the new order info) you can convert the buffer to a string and then parse it:
- If you’re using more than one webhook, consider abstracting the verification into it’s own middleware.
- If you’re using body-parser you might have a hard time getting the raw body (errors like: ‘data must be string or buffer’), here’s a workaround.
- To combat timing attacks, consider using crypto.timingSafeEqual