Shopify
Attribute paid Shopify orders and refunds to the visits that led to them.
Use this connector when you sell through a Shopify Online Store. Seean verifies each webhook with the store’s signing secret, records orders/paid webhooks as orders and matches refunds/create webhooks to them. Attribution is derived on Seean’s server from the buyer’s tracked visits when the order’s note attributes carry the visitor ID. The tracker itself is installed as described in Install on Shopify.
Setup
- In Seean open Settings → Integrations → Payment providers on your own website, choose Shopify, select Enable and copy the webhook URL (
https://events.seean.io/api/connect/shopify/<site ID>; use the exact value shown). - In the Shopify admin open Settings → Notifications → Webhooks and create two webhooks in JSON format pointing at that URL: Order payment (
orders/paid) and Refund create (refunds/create). - The Webhooks page shows the key your webhooks are signed with; paste it into Seean, then choose Save secret. Webhooks subscribed by a custom app are signed with that app’s client secret instead; paste that value if you subscribe through an app.
- Keep the standard tracker snippet in
theme.liquid: on a Shopify storefront it writes the cart attributes described below automatically.
Only the website owner can connect, replace or disconnect a provider; one connection per provider; demo websites cannot connect providers.
Attribution handoff
Shopify delivers cart attributes as note_attributes on the order.
Automatic cart attribution
When the tracker runs on an Online Store theme (it detects window.Shopify.routes.root), it copies the attribution into the cart itself: after the first pageview has answered with the visitor identity, it sends one small POST cart/update.js request with seean_visitor_id, seean_landing_path, seean_converting_path and, when present, seean_campaign. The request is repeated only when those values change - in practice once per page view, because the converting path follows the page - and never when they are unchanged, so a checkout that starts from the cart carries the latest page. The request is fire-and-forget: the storefront never waits for it, a failed request is ignored, nothing runs for visitors who opted out or when the tracker is blocked, and nothing is written to browser storage (a first pageview whose collection request fails simply syncs on the next one). Add data-shopify="off" to the snippet to disable it (for example on a headless storefront that writes attributes itself). Checkout pages run no theme scripts, so the tracker is not involved there.
Not supported by the automatic handoff: Buy It Now and other dynamic checkout buttons, Shop Pay started directly from a product page, and any flow that skips the cart - those orders have no cart attributes and arrive Unattributed. Shop Pay started from the cart keeps the attributes. Custom pixels are sandboxed and cannot run the tracker; headless (Hydrogen) storefronts should write the attributes with their own cart mutations using the snippet below.
Manual handoff
If you disabled the automatic handoff, write the Seean keys to the cart with the Ajax Cart API before the visitor reaches checkout, for example in your theme after the tracker has loaded:
await window.seean?.ready;
const a = window.seean?.attribution?.() ?? {};
if (a.visitorId) {
const attributes = { seean_visitor_id: a.visitorId, seean_landing_path: a.landingPath, seean_converting_path: a.convertingPath, seean_campaign: a.campaign };
fetch(window.Shopify.routes.root + "cart/update.js", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ attributes: Object.fromEntries(Object.entries(attributes).filter(([, v]) => v)) }),
});
}
Run it once per page view so the converting path stays current; attributes persist on the cart until checkout.
| Key | Meaning |
|---|---|
seean_visitor_id |
Links the order to the buyer’s tracked visits; touches, first and last source, landing page and time to buy are derived from that history. |
seean_landing_path |
Used only when Seean has no tracked visit for the visitor in the 90 days before the order. |
seean_converting_path |
The page where checkout started. |
seean_campaign |
Overrides the campaign derived from the tracked history. |
Orders without seean_visitor_id are recorded as Unattributed. Cart attributes reach the order on the standard Add to cart → Checkout flow; Buy It Now, Shop Pay and other accelerated checkout buttons may skip the cart, in which case those orders arrive unattributed. Seean has not verified these flows against a live Shopify store; verify your own store’s checkout after installing.
What is recorded
| Event | Result in Seean |
|---|---|
orders/paid |
One-time order with total_price in the shop currency. |
refunds/create |
Refund of order_id by the sum of the refund’s successful refund transactions. |
The order ID is the numeric Shopify order ID, the product name is the first line item’s title (then the order name) and the order time is processed_at. Other topics, including orders/create, are acknowledged and ignored. Seean reads the topic from the X-Shopify-Topic header.
Refunds
refunds/create reduces the order by the refunded transaction amounts, once per refund ID; partial refunds are supported and failed transactions are not counted. Refunds lower net revenue in the period of the original order. If the original order has not arrived, Seean returns HTTP 503 and shows Last event failed: Order not found. The provider can retry after the order arrives; the refund ID prevents double counting. A refund whose order is still missing one day after the refund was created is acknowledged and ignored, so refunds of payments Seean never recorded do not keep the endpoint failing; record those with the Order API. See delivery recovery if retries expire.
Currency
Orders are stored in the website’s reporting currency (Settings → Integrations → Revenue integration). Shopify’s decimal total_price is converted with the ISO 4217 exponent ("409.94" USD is 40994 minor units). An order in another currency, such as a multi-currency storefront sale, is acknowledged but not stored, and the reason appears under the provider.
Verify
Choose Send test notification next to the webhook in the Shopify admin (the sample order is stored only if it is paid), or place a test order with Shopify’s Bogus Gateway. The status line under Shopify shows the last event time and, if the last event could not be stored, the error. Then open Revenue for a range containing the order and check the amount, product and derived attribution. Duplicate deliveries return duplicate and change nothing.
Limits
- Seean verifies the
X-Shopify-Hmac-Sha256header (base64 HMAC-SHA256 of the body). Shopify does not sign a timestamp, so a captured delivery could be replayed; replays are deduplicated by order and refund ID and cannot create a second order. - Every Shopify order is recorded as a one-time order; subscription apps that create renewal orders produce separate paid orders.
- Request bodies above 64 KiB are rejected with 413. Topics Seean does not handle are acknowledged with 200; rate limits return 429 and temporary ingestion failures return 503 so the provider can retry. See delivery recovery.
- Disable stops accepting events (the URL answers 404); delete the webhooks in Shopify so it stops retrying. Existing orders are kept.
Related: Revenue attribution, Order API and refunds, Install on Shopify.
Something unclear? Contact support.