message.comDevelopers

Sync Shopify orders to message.com

Bring purchase history into the inbox. When a Shopify order is created or a customer is updated, push the data into the matching message.com visitor record. Agents see order count and lifetime value; AI grounds replies in actual purchase data.

When the Built-for-Shopify app ships, this is a one-click toggle inside the Shopify admin. Until then, this DIY webhook bridge is the equivalent. See Shopify plugin.

Architecture

  1. Shopify fires a webhook on orders/create (and optionally orders/updated, customers/update).
  2. Your server verifies the HMAC signature.
  3. Your server calls POST /api/v1/visitors/upsert on message.com with the order attributes.
  4. The matching visitor in message.com (by email or external ID) is updated.

1. Build the webhook handler

The handler verifies the signature, parses the order, and forwards a visitor upsert.

server.ts
// Node / Express handler for the Shopify orders/create webhook
import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/shopify/orders-create", async (req, res) => {
  const hmac = req.get("X-Shopify-Hmac-Sha256")!;
  const digest = crypto
    .createHmac("sha256", process.env.SHOPIFY_WEBHOOK_SECRET!)
    .update(req.body)
    .digest("base64");
  if (digest !== hmac) return res.status(401).end();

  const order = JSON.parse(req.body.toString());

  // Forward to message.com as a visitor upsert
  await fetch("https://app.message.com/api/v1/visitors/upsert", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MESSAGE_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": String(order.id),
    },
    body: JSON.stringify({
      email: order.email,
      externalId: `shopify:${order.customer?.id}`,
      attributes: {
        lastOrderId: order.id,
        lastOrderTotal: Number(order.total_price),
        lastOrderAt: order.created_at,
        ordersCount: order.customer?.orders_count,
        totalSpent: Number(order.customer?.total_spent),
      },
    }),
  });

  res.status(200).end();
});

Verify the signature on the raw bytes before parsing JSON. Re-serialised JSON does not produce the same HMAC.

2. Register the webhook with Shopify

bash
# Register the webhook via Shopify Admin API
curl -X POST \
  -H "X-Shopify-Access-Token: shpat_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook": {
      "topic": "orders/create",
      "address": "https://your-server.com/webhooks/shopify/orders-create",
      "format": "json"
    }
  }' \
  https://your-store.myshopify.com/admin/api/2025-01/webhooks.json

You can also register from the Shopify admin under Settings → Notifications → Webhooks, but the API call is reproducible across environments.

3. Add useful topics

Register additional topics as your needs grow:

  • orders/updated. fulfillment status, edits.
  • orders/cancelled. mark the order cancelled in attributes.
  • customers/create, customers/update. keep email and name fresh.
  • refunds/create. flag refunded orders.

4. Verify

  1. Place a test order in Shopify.
  2. Watch your server logs for the inbound webhook.
  3. Open the matching customer in app.message.com. The order attributes (count, total spent, last order) should appear in the visitor profile panel.
  4. Start a chat as that customer. Confirm the agent sees the attributes.

Common pitfalls

  • Forgetting Idempotency-Key. Shopify retries webhooks up to 19 times over 48 hours. Without an idempotency key, retries can clobber attributes. Use the order ID.
  • Guest checkouts. Orders from anonymous customers have customer = null. Decide whether to upsert by order email anyway.
  • Webhook tolerance. Shopify expects a 2xx response within five seconds. If your upstream call to message.com is slow, ack the webhook first and enqueue the forward.
  • Multi-store. If you run several Shopify stores against one message.com workspace, namespace external IDs (e.g., shopify-us:cust_123, shopify-eu:cust_123).

Next steps