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
- Shopify fires a webhook on
orders/create(and optionallyorders/updated,customers/update). - Your server verifies the HMAC signature.
- Your server calls
POST /api/v1/visitors/upserton message.com with the order attributes. - 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.
// 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
# 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.jsonYou 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
- Place a test order in Shopify.
- Watch your server logs for the inbound webhook.
- Open the matching customer in
app.message.com. The order attributes (count, total spent, last order) should appear in the visitor profile panel. - 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
- Install the widget on Shopify.
- Identify logged-in users with HMAC.
- Sync Stripe customers for non-Shopify revenue data.