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.

Creating a webhook

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:

Confirming our server is receiving the notification

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:

Final thoughts

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 as 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