Verifying Shopify webhooks with NodeJS & Express

Scott Dixon
Aug 27, 2018 · 2 min read

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:

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

Scott Dixon

Written by

🏄 Gold Coast, Australia

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade