Author picture Alexandre Bouchard

How to Synchronize Orders with Shopify Webhooks

Published · Updated


Shopify is a broad platform but for any ambitious tech team, it doesn't do it all. There are literally thousands of valid use cases for Shopify Webhooks. Today I'll run you through how you can leverage the order/created, order/updated, and order/deleted webhook topics to synchronize your Shopify orders with your database and services.

I had to rely on it to at Rachel. We implemented a webhook integration to synchronize all our orders in an Elasticsearch index that we would use to do complex segmentation queries to optimize the fulfillment sequence of orders and operational efficacy.

For our rundown of how this set this up, I'll be using Shopify REST API, Javascript, and ExpressJS.

You can also take a look at this general tutorial to create your first shopify webhook.

Building Shopify webhook integration to synchronize orders in Elasticsearch index

Shopify webhooks are configured with a given set of topics that represent which action on which resource we want to be notified for an URL. Shopify will make an HTTP POST request to that URL with the data of the resource. In our case, the topics order/created and order/updated will receive a request with the newly created or updated order. The order/deleted will receive a request with the ID of the delete order.

There are a few important things we will need to do to be properly setup.

Create a HTTP endpoint to receive the webhooks

Our endpoint need to accept unauthenticated POST request and parses the JSON body data of the webhook. This endpoint will need to be available via HTTPS.

const express = require("express");
const app = express();
const port = 3000;

// Shopify will send data as JSON, register the express JSON parser
app.use(express.json());

// Expose our endpoint
app.post("/webhooks/order", (req, res) => {
  console.log(req.body);
  res.send("Server receive a order!");
});

app.listen(port, () => {
  console.log(`Order service listening at http://localhost:${port}`);
});

Register a webhook on Shopify

To register your webhook, we'll need to expose the server publicly via HTTPS. Hookdeck CLI is a great tool just for that.

hookdeck listen 3000
// npm install shopify-api-node
const ShopifyApi = require("shopify-api-node");

const shopify = new ShopifyApi({
  shopName: "SHOPIFY_STORE_NAME",
  apiKey: "SHOPIFY_API_KEY",
  password: "SHOPIFY_PASSWORD",
  apiVersion: "2020-01",
});

shopify.webhook
  .create({
    topic: ["order/created", "order/updated", "order/deleted"],
    address: "HOOKDECK_HTTPS_HOSTNAME" + "/webhooks/order",
  })
  .then(console.log)
  .catch((e) => {
    console.error("Shopify - Registration failed for:", topic);
  });

Verify the signature

Because webhook endpoints are unauthenticated, we need to verify the request Signature by using the header X-Shopify-Hmac-Sha256 to verify Shopify is the original sender. Your secret will be found in your app or private app configuration.

const crypto = require("crypto");

// Secret provided by Shopify when creating the webhook
const secret = "YOUR_SECRET";

// Middleware to verify the signature
const verifyShopifyWebhookSignature = (req, res, next) => {
  // Retrieve hmac signature from headers
  const hmacHeader = req.get("X-Shopify-Hmac-Sha256");
  // Calculate hash from body and secret value using sha256
  const hash = crypto
    .createHmac("sha256", secret)
    .update(Buffer.from(req.body))
    .digest("base64");

  // Fail with 401, Unauthorize
  if (hash !== hmacHeader) {
    res.sendStatus(401);
  } else {
    // Proceed
    next();
  }
};

// Register the middleware for the endpoint
app.post("/webhooks/order", verifyShopifyWebhookSignature, (req, res) => {
  console.log(req.body);
  res.send("Server receive a order!");
});

Implementing the synchronize method

That's all well and good but we aren't actually doing anything yet other then logging the data we received. Let's send synchronize to Elasticsearch.

Ensuring our synchronization method is idempotent

Shopify may (and will) send the same webhook more than once as they operate on an at-least-once strategy. While deleting and updating an order is by nature idempotent (the action can be repeated multiple times with the same results), creating an order is not. To make sure we don't accidentally end up duplicating orders, we will use the Shopify order id as our idempotency key by setting the elastic search entry id to be our Shopify order id.

// npm install @elastic/elasticsearch
const elasticsearch = require("@elastic/elasticsearch");

const client = new elasticsearch.Client({
  node: "ELASTICSEARCH_SEARCH_URL",
  auth: {
    username: "ELASTICSEARCH_USER",
    password: "ELASTICSEARCH_PASSWORD",
  },
});

const index = "ELASTICSEARCH_INDEX";

const syncToElastic = (topic, order) => {
  if (topic === "order/deleted") {
    return client.delete({ index, id }).catch((err) => {
      if (err.meta && err.meta.statusCode === 404) {
        // Order was not found, and therefore already deleted.
        // That's to be expected since you can receive the same webhook multiple times
        return null;
      }
      throw err;
    });
  }
  // Set order id as elastic entry id to make the request idempotent.
  // Subsequent create with the same id will act as an update
  return client.index({ index, body: order, id: order.id });
};

app.post("/webhooks/order", verifyShopifyWebhookSignature, (req, res) => {
  syncToElastic(req.headers["X-Shopify-Topic"], req.data)
    .then(() => {
      res.send("Server receive a order!");
    })
    .catch((e) => {
      console.error(e);
      res.sendStatus(500);
    });
});

Before going to production

Now that we have a working integration, there are a few things we must talk about. Going to production has its challenges specifically with webhooks. Missing one or failing to properly synchronize has consequences. We published a whole guide to help you with this, you can find it right here.

Conclusion

There you have it! Once deployed, you will now be synchronizing all your order data to your elastic search index. The same logic can be applied to index your own databases like Postgres or MongoDB and perform any custom logic that applies to your company. Rachel has much more use cases for this webhook such as setting custom computed tracking value in our CRM and creating special gift cards when a specific product is purchased. It's up to you to be creative and leverage Shopify webhooks to simplify your operations or create new opportunities.