E-commerce messaging usually runs one of two ways. The batch way: export abandoned checkouts to a CSV overnight, upload a segment, send the next morning. The triggered way: the store emits an event the moment a buyer stalls, and the message is a function of that event, not of a campaign calendar. Only the second one catches a buyer while the cart is still open in another tab. Devotel Orbit's Shopify integration wires a store's customers, checkouts, orders, and refunds into Orbit as first-class events over an HMAC-verified webhook receiver, with an hourly poll behind it. This post covers why the triggered model wins, what the integration subscribes to, a worked abandoned-cart WhatsApp recovery flow, the GDPR topics Shopify mandates on the same endpoint, and the numbers that tell you the flow is working.
Why commerce-triggered messaging beats batch campaigns
The difference is structural, not stylistic. Three properties separate an event-driven recovery flow from a nightly blast:
- Freshness. A checkout event reaches Orbit seconds after the buyer enters checkout and stalls. A batch export is hours old by the time it sends, and hours is where cart intent decays.
- Self-cancellation. A triggered flow can watch for the buyer's own
orders/createand exit the recovery branch the moment the cart closes on its own. A batch campaign has already spent its send on people who completed the purchase hours earlier. - Identity continuity. The webhook upserts an Orbit contact keyed on the Shopify customer or checkout id, so the recovery message, the later order, and the post-purchase survey all land on one contact row. CSV imports deduplicate on address strings; event streams merge on provider ids.
Batch still has its place — a seasonal promo to a segment is not an event. Recovery, replenishment, and order lifecycle messaging are events, and they underperform when forced through the campaign calendar.
What the integration subscribes to
On OAuth connect (owner/admin only), Orbit auto-registers the store for the canonical operational topic set, and re-connects are idempotent:
| Topic | What arrives in Orbit |
|---|---|
customers/create | Contact upsert for the new shop customer |
checkouts/create | Contact upsert plus the normalized cart.created signal — the abandonment seed |
orders/create, orders/paid, orders/fulfilled | Order event records (placed, paid, shipped) |
refunds/create | Order event record (refunded) |
app/uninstalled | Connection marked inactive when the shop disconnects |
Every inbound event hits one endpoint — POST /api/v1/integrations/webhooks/shopify — with four properties worth knowing before you build on it:
Verified, fail-closed. Shopify signs each delivery with a base64 HMAC-SHA256 of the raw body in X-Shopify-Hmac-Sha256, keyed with the app shared secret. The comparison is constant-time: a wrong signature gets 401, an unset secret gets 503. The receiver never silently accepts unsigned traffic.
Tenant-resolved by header. The HMAC is the sole auth; the receiver resolves which tenant owns the event from the X-Shopify-Shop-Domain header, probing a shop-domain reverse map with a stamped fallback so repeat webhooks resolve in O(1).
Idempotent. Deliveries carry a unique X-Shopify-Webhook-Id. The receiver persists each event keyed (shop_domain, webhook_id) and Shopify's retries arrive as no-ops, so exactly-once processing survives Shopify's at-least-once delivery.
Fast. The receiver returns inside Shopify's 5-second budget by enqueuing heavy downstream work instead of awaiting it on the request path.
Behind the real-time path, an hourly poll refreshes customers, orders, products, and abandoned-checkouts. If a delivery is missed or a registration partially failed, freshness degrades to an hour rather than to never — and re-running the connect flow re-registers idempotently.
Worked example: abandoned-cart WhatsApp recovery
The recovery flow is four steps, and each one maps to an integration behavior above.
1. Trigger on `cart.created`. Shopify fires checkouts/create the moment a buyer enters checkout — long before any order exists. The receiver upserts the contact and emits event.type = "cart.created" carrying recovery_url (Shopify's abandoned_checkout_url), total_price, currency, and the external checkout id. That emit is what your flow subscribes to.
2. Delay. Wait long enough for the buyer to finish unassisted — an hour is a reasonable starting point. Immediate sends convert worse than they should because they interrupt people who were still checking out.
3. Exit on self-completion. Before sending, check whether an orders/create arrived with the same checkout token. If it did, exit the branch — the cart closed on its own and a recovery message is now an annoyance.
4. Send the WhatsApp recovery template. WhatsApp requires a pre-approved template for business-initiated messages, so the send is a template call with the cart fields as body parameters:
curl -X POST https://api.orbit.devotel.io/api/v1/messages/whatsapp \
-H "X-API-Key: dv_live_sk_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+14155552671",
"type": "template",
"template": {
"name": "cart_recovery",
"language": { "code": "en" },
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "{{cart_total}} {{currency}}" },
{ "type": "text", "text": "{{recovery_url}}" }
]
}
]
}
}'Guest checkouts work here too. When a checkout carries only an email or phone, the receiver upserts with externalId = "checkout:<checkout_id>" — a stable handle the drip can target — and the contact row merges by email/phone when the buyer later logs in. The buyer who never created an account is still reachable, and their history consolidates when they identify.
For the full flow-definition shape, start from the order/updates recipe in Five flow recipes and substitute the cart.created trigger plus these checkout fields.
GDPR-mandated topics on the same endpoint
Shopify requires every Public app to answer three compliance webhooks, configured at the Partner-app level rather than per store. Orbit implements them on the same HMAC-verified receiver, which matters to buyers evaluating the integration: one endpoint, one signature path, one audit trail.
| Topic | What Orbit does |
|---|---|
customers/data_request | Accept and log the request; the export is fulfilled against the published privacy policy within the 30-day SLA. |
customers/redact | Soft-delete the matched contact row; the tenant-scoped data-retention sweeper completes the hard redaction asynchronously. |
shop/redact | Fires 48 hours after uninstall; marks the connection inactive and enqueues a tenant-scoped hard redaction. |
All three return 200 regardless of internal outcome, so Shopify's compliance harness never retries a terminal request. The controls around them stay tenant-owned: your privacy policy, your retention sweeps, your opt-out keywords, quiet hours, and consent windows are settings you hold — the integration answers Shopify's mandated topics and leaves the posture decisions to you.
KPIs to instrument
Instrument the flow before you call it done. Five numbers cover it:
- Recovery rate. The fraction of triggered
cart.createdflows that end in an order attributable to the recovery message, on an attribution window you pick and hold constant. This is the headline metric; everything else explains it. - Early-exit rate. The fraction of flows exiting at the self-completion check. A high exit rate with a low recovery rate means your delay is doing the converting — shorten it and see whether the message starts earning its keep. A near-zero exit rate with a low recovery rate means the message itself is weak.
- Delivered and read rates on the recovery step. Per-recipient channel truth from the message log, split by destination market. A delivery dip in one market is a template or quality-rating problem, not a flow problem.
- Opt-out rate on the recovery template. STOP responses attributed to the recovery send. Cart recovery lives inside a consent scope; a rising opt-out rate means the cadence or copy has overreached that scope, and the fix is the message, not the targeting.
- Event freshness lag. Time from Shopify checkout event to flow trigger. Sustained lag means you silently fell back to the hourly poll — check the registration state on the connection record and re-run the connect flow.
Build it
The operator walkthrough — OAuth connect, auto-registration, the receiver's verification and idempotency contract, failure modes, and troubleshooting — is in the Shopify integration guide. The messaging surface the recovery step sends through is the WhatsApp channel, and the flow-definition patterns the trigger plugs into are in Five flow recipes. Connect a store, trigger on cart.created, and the first recovery send is an afternoon's work.
Frequently asked questions
Does Orbit answer Shopify's mandatory GDPR webhooks?
Yes. The three compliance topics — customers/data_request, customers/redact, and shop/redact — are implemented on the same HMAC-verified receiver as the operational topics, and all three return 200 regardless of internal outcome so Shopify's harness doesn't retry a terminal request.
What happens if Shopify's webhook delivery is missed?
An hourly poll refreshes customers, orders, products, and abandoned checkouts, so a missed delivery degrades freshness to an hour instead of losing the event. Re-running the connect flow re-registers the topics idempotently to restore the real-time path.
Do guest checkouts get recovery messages?
Yes. A checkout with only an email or phone upserts a contact under a stable checkout-id handle, so the drip can target it, and the row merges by email/phone when the buyer later logs in.
Why does a test request return 401 or 503?
Both are the receiver's expected fail-closed behavior. A wrong HMAC returns 401; an unset app shared secret returns 503 with the integration disabled. Neither silently accepts unsigned traffic.
Does the recovery flow cancel if the buyer completes the purchase?
Yes, if you build the exit check — before the send, look for an orders/create with the same checkout token and exit the branch. The order event and the checkout event land on the same contact row, so the check is a property read, not a lookup.
The takeaway
Triggered commerce messaging beats the batch calendar on freshness, self-cancellation, and identity — and the integration behind it is a verified, idempotent, audit-friendly endpoint with an hourly poll for the misses. The Shopify integration guide has the connect walkthrough; the WhatsApp channel docs carry the template-send surface the recovery step uses.
Published 28 August 2026.