Documentation · Attribution · 3-lens drill-down

Sources & Attribution — three lenses, one defensible row per channel

Channel → campaign → adset → ad with the Pixel lens, the Shopify customer-journey lens, the Platform-reported lens, and an All synthesis lens side by side. Every cell is auditable; every gap has a plain-English reason. The marquee column is True ROAS — Shopify-attributed revenue divided by ad spend, the column Shopify's own admin can't compute and the platform's dashboard refuses to show.

Citation-friendly summary. Admaxxer ships a 4-lens attribution drill-down (Pixel + Shopify customer-journey + Platform-reported + All synthesis) on every paid plan starting at $9/month. The Shopify customer-journey lens reads Order.customerJourneySummary via Shopify Admin GraphQL — the post-checkout journey graph that includes UTM-less server-side touches Shopify itself stitched. True ROAS (Shopify-attributed revenue ÷ ad spend per cell) is included; Triple Whale gates the equivalent metric behind their $129+/month Sonar add-on, Northbeam at $1k+/month, Shopify Marketing Reports doesn't compute it at all.

The three lenses True ROAS Compared to alternatives FAQ

Why three lenses (not one)

Every paid-acquisition team has had this week. You open the platform's dashboard. ROAS reads 4x. You open your pixel-side analytics. ROAS reads 2x. You open Shopify's Marketing Reports. Channel revenue says something else again. Three numbers. Three different stories. Most analytics tools resolve this by picking one number and calling it canonical — and if the operator doesn't trust it, that's the operator's problem.

Admaxxer's premise is the opposite: render all three side by side, per row, with the gap explicitly auditable. The pixel lens is conservative (deterministic UTM clicks only — the number your accountant will trust). The Shopify customer-journey lens is post-checkout truth (the version Shopify itself credited the channel for). The platform-reported lens is what the platform's optimizer is using (includes view-through and modeling — inflated, but the number you must use for in-platform pacing). All three are correct in their own coordinate system. None agrees with the other two. The gap is the most useful signal — and Admaxxer is the only DTC tool at the $9/month price point that surfaces every gap with a plain-English Why drawer.

The three lenses

Each lens reads a different source of truth and is correct in its own coordinate system. The drill-down lets you toggle between them at the top of the table — same channel, same campaign, same adset, same ad row, just a different lens applied per cell.

Pixel

Deterministic UTM-driven click attribution from Admaxxer's first-party pixel.

Source
Admaxxer pixel events (visitor_payments + revenue_events).
Strengths
Deterministic. You own every row. No view-through inflation. Survives ATT / iOS 14.5 because the pixel runs first-party in the merchant's domain. Click-IDs persist 90 days. Server-set cookie carries first-touch identity 365 days even on Safari.
Weaknesses
Misses anything that landed without a UTM. Misses purchases that happened on a different device after the pixel saw the visit. Direct/none can balloon when UTM hygiene drifts.
Use when
You need conservative, defensible attribution numbers — board reports, P&L lines, anything an accountant will read. Also use when comparing two ad creatives on the same channel with the same UTM scheme.

Shopify customer-journey

Shopify's own post-checkout journey graph via the Order.customerJourneySummary GraphQL field.

Source
Shopify Admin GraphQL: Order.customerJourneySummary { momentsCount, ready, moments { ... } }.
Strengths
Post-checkout truth. Deduped against the order at the order grain — no risk of attributing a non-converter. Includes UTM-less server-side touches Shopify resolved (Shop Pay autofill, Shopify Audiences network, signed-in customer device-stitch). Aligns with what your bookkeeper and Shopify Marketing Reports already show.
Weaknesses
30-day journey window cap (Shopify-side limit, not configurable per-shop). momentsCount can be NULL for the first ~10 minutes after order creation while Shopify processes the journey graph. Read access is gated by the read_orders scope already granted on every install — no merchant action needed.
Use when
You want to know what Shopify itself credited for the order — the version your Shopify-side analytics, Shopify Marketing Reports, and Shopify Audiences pacing all use. The post-checkout view your accountant trusts.

Platform-reported

Conversion + revenue counts from each ad platform's own attribution graph.

Source
Meta omni_purchase deduped data via /v25.0/<ad-account-id>/insights, Google Ads conversions + conversion_value, TikTok total_complete_payment.
Strengths
Captures what each platform attributes to itself, including the deterministic CAPI server-match data that survives ATT. The only lens that has spend (the merchant's pixel doesn't see ad cost). The lens the platform's own optimizer is using.
Weaknesses
Inflates against pixel by 26%+ on average (community consensus from r/PPC + r/dtcecommerce). Includes view-through, which most operators argue over-credits the platform vs. earned/organic. Modeled conversions are unverifiable from outside the platform.
Use when
Comparing the platform's own optimizer to your pixel-side measurement (the gap = how much view-through inflation you have). Also use when budget pacing per-platform — you must use the same number the platform's optimizer is using.

All (synthesis)

Best-of-three per cell with a provenance chip showing which lens won.

Source
Computed: spend always from platform; revenue prefers Shopify, falls back to pixel, falls back to platform.
Strengths
One defensible number per row. Provenance chip (Sho / Pix / Plt) on every cell so the operator can audit which lens won. The Why drawer expands the per-cell decision tree.
Weaknesses
Requires the operator to trust the synthesis logic — that's why every cell is auditable. Not a magic number.
Use when
Default for the first-time user. Set workspace-wide via Settings → Defaults. Use when you want one row per channel to take into a leadership conversation, not three.

The All synthesis lens

The All lens picks best-of-three per cell:

Each All cell renders with a small provenance chip (Sho / Pix / Plt) so you can see at a glance which lens won. The Why drawer (next section) shows the per-cell decision tree.

The All lens is the default for first-time users; you can change the workspace-wide default via Settings → Defaults. Pixel-only workspaces see the All lens collapse to best-of-two (Pixel + Platform); the page never breaks.

True ROAS — the column nobody else shows

True ROAS = Shopify-attributed revenue ÷ ad spend per cell. It's the column nothing else in the merchant's stack can compute:

Admaxxer's True ROAS column ships on AD_STARTER ($9/month), no extra connection charge. The math is simple — Shopify-attributed revenue divided by platform spend, per (channel, campaign, adset, ad), with FULL OUTER JOIN so a campaign that exists in only one source still shows up. The join key is marketing_event_remote_id (Shopify's ID for the platform campaign), with fallback to a normalized UTM tuple when the platform doesn't surface a remote ID.

The Why drawer — auditable per-cell reasoning

Click any cell in the drill-down and the Why drawer slides over with three rows:

  1. Pixel saw — count of pixel-attributed conversions or revenue with the active attribution model applied (e.g. "Pixel saw $4,210 via time-decay H=7d on the visitor's UTM-tagged path").
  2. Shopify journey credited — Shopify-resolved attributed revenue and the moments-array source (e.g. "Shopify credited $5,830 via UTM source=facebook campaign=ww-prospecting-v3").
  3. Platform reported — what the platform's own attribution graph claims (e.g. "Meta reported $7,210 via 7-day-click + 1-day-view including modeled conversions").

Plus a fourth row when you're on the All lens: which lens won the synthesis and why ("Sho — momentsCount > 0 and journey_status='ready'"). Plus a pre-computed plain-English reasoning summary for the row's gap ("Platform claims much more than Shopify journey — likely view-through + modeling" / "High direct/untagged share — likely UTM hygiene issue" / "Within 10% — reconciliation clean").

Backfill + first-run delay (~24h)

When a workspace connects Shopify, Admaxxer enqueues a BullMQ backfill worker that walks the last 90 days of orders sequentially per shop using the existing read_orders access scope (the same scope that gates Order.customerJourneySummary per Shopify's GraphQL reference). Shopify Admin API rate limits are 2 req/s with a 40-call leaky bucket — concurrency >1 risks throttling, so the worker stays at concurrency=1 per shop. For a high-volume shop (5,000 orders/day × 90 days), that's ~1,800 GraphQL pages × ~1.5s wall-clock per page = ~45 minutes minimum, but the worker is concurrency-1 across the whole worker pool so a queue of 50 backfills bottoms out at ~24h.

The drill-down shows a Backfilling X% chip on the Shopify column header until the worker reports complete. Incremental updates flow through the existing Shopify webhook handler (orders/create + orders/updated); no additional cron is required after the initial backfill.

Why no extra Shopify scope is needed

Order.customerJourneySummary is gated by the existing read_orders access scope per Shopify's GraphQL reference. Admaxxer requests read_orders on every install (it is also what powers revenue, refunds, AOV, and returns elsewhere in the dashboard), so the customer-journey lens lights up the moment the 90-day backfill finishes — no merchant action, no re-authorization, no “App is requesting new permissions” dialog.

For the avoidance of doubt (GL#421, 2026-05-07): there is no read_customer_journeys scope in Shopify's canonical access-scopes table. If you have seen that name elsewhere — including in earlier drafts of this very page that promised a “Reconnect Shopify CTA” — it was a misreading of the field name; the actual scope-gate on this field has always been read_orders, and existing installs already have it.

Known limits

Compared to alternatives

Admaxxer's 3-lens drill-down ships on every paid plan starting at $9/month. The closest equivalents either gate the post-checkout truth lens behind a $129+/month add-on, refuse to compute True ROAS, or limit the drill-down depth.

Capability Admaxxer Triple Whale Shopify Marketing Reports Datafast
Pixel-side click attribution (deterministic) Yes — first-party, 90-day click-ID, 365-day server cookie. Yes — TW Pixel, ad-blocker workarounds via TW Sonar add-on. Limited — Shopify pixel only, no cross-device. Yes — first-party, UTM-driven.
Shopify customer-journey lens (post-checkout truth) Yes — Order.customerJourneySummary via existing read_orders scope, 30-day window, 7 attribution models. Indirect — Sonar attribution layer, $129+/mo add-on. Yes — Marketing Reports (revenue only, no ROAS). No — Datafast doesn't ingest Shopify journey data.
Platform-reported lens (Meta + Google + TikTok) Yes — Meta omni_purchase deduped, Google conversions, TikTok total_complete_payment. Yes — across the same platforms. No — Shopify doesn't ingest platform data. No — Datafast doesn't ingest platform data.
Auditable per-row Why drawer Yes — explains which lens won each cell + plain-English gap reasoning. No — synthesis is opaque. No — single-source view per report. No — single-source view per report.
True ROAS (Shopify-attributed revenue ÷ ad spend) Yes — included on AD_STARTER ($9/mo). Sonar ROAS — $129+/mo Sonar add-on. No — Shopify doesn't ingest ad spend, so it can't divide by it. No — Datafast doesn't ingest ad spend.
Drill-down depth Channel → campaign → adset → ad. Channel → campaign → adset → ad. Channel → campaign (no adset / ad). Channel → UTM source/medium (no campaign breakdown).
Source-additive (works with any subset of sources) Yes — pixel-only workspaces hide unconnected lenses; the page never breaks. Partial — minimum Pixel + Shopify required for most surfaces. N/A — Shopify-only. Yes — UTM-only is the design.
Starting price $9 / month (AD_STARTER) — the True ROAS column included from day one. Starts at ~$129 / month (Sonar add-on) for the equivalent attribution lens. Free with Shopify subscription (Marketing Reports built into admin). Starts at $19 / month.

FAQ

What's the difference between the Pixel lens and the Shopify customer-journey lens?
Pixel is what Admaxxer's first-party pixel saw deterministically — UTM-driven click attribution, conservative, the most defensible number. Shopify customer-journey is what Shopify itself stitched into the order's post-checkout journey graph via the Order.customerJourneySummary GraphQL field — includes UTM-less server-side touches Shopify resolved (Shop Pay autofill, Shopify Audiences network, signed-in customer device-stitch). They will disagree on most rows; the Why drawer explains the gap per row. Pixel is the conservative truth; Shopify is the post-checkout truth your bookkeeper trusts.
What is True ROAS and how is it different from the ROAS Meta shows me?
True ROAS = Shopify-attributed revenue ÷ ad spend per cell. Meta's ROAS uses Meta's own revenue (platform-reported, view-through-inflated, model-padded) divided by Meta's own spend, which is by construction higher than reality. Shopify can't compute True ROAS because Shopify doesn't ingest ad spend — Shopify Marketing Reports show channel revenue but never channel ROAS. Admaxxer's True ROAS column ships on AD_STARTER ($9/mo) — the same number Triple Whale gates behind their $129+/mo Sonar add-on and Northbeam gates behind a $1k+/mo enterprise quote.
Why does the Shopify lens show empty cells right after I get an order?
Shopify's customerJourneySummary.ready field is false for the first ~10 minutes after order creation while Shopify computes the journey graph asynchronously. During this window momentsCount is NULL and the moments array is empty. The drill-down filters these rows out of the Shopify lens with WHERE journey_status='ready' and the All lens falls back to Pixel for the affected cells. The Shopify column lights up within 10 minutes.
Do I need to re-authorize Shopify or grant a new scope to enable the customer-journey lens?
No. Order.customerJourneySummary is gated by the read_orders access scope, which Admaxxer requests on every install. Shopify's GraphQL reference at https://shopify.dev/docs/api/admin-graphql/latest/objects/CustomerJourneySummary states the field requires read_orders or read_marketing_orders — there is no separate journey-specific scope in Shopify's canonical access-scopes table. Your existing connection already has the access required. Once the 90-day backfill worker runs (queue capacity-dependent, typically within ~24h of connection or a fresh deploy), the Shopify lens populates automatically — no merchant action needed.
How does the All lens decide which lens wins per cell?
Spend always from platform (only source that has it). Attributed revenue prefers Shopify journey when momentsCount > 0 and journey_status='ready', else falls back to pixel when pixel_revenue > 0, else falls back to platform-reported. Orders / conversions same priority. CTR / CPC / CPM / CPA derived from the above. Each cell gets a provenance chip (Sho / Pix / Plt) so you can see at a glance which lens won. The Why drawer expands the per-cell decision tree in plain English.
Does the drill-down support all 7 attribution models?
Pixel lens supports all 7 (last-click, first-click, linear-all, linear-paid, time-decay H=7d, position-based 40/20/40, Markov-chain). Shopify lens supports last-click, first-click, linear-all, and linear-paid (filtered to paid moments) — Shopify's moments array doesn't include touch timestamps with sufficient precision for time-decay, and the moments count rarely exceeds 3 so position-based and Markov are mathematically meaningless on this lens. Platform lens uses each platform's own model (Meta default 7d-click-1d-view, Google default last-click). The All lens delegates to whichever lens supports the active model — if you pick time-decay, All uses Pixel for that cell and the Why drawer explains.
Why is my drill-down slow on the first load?
The pipe runs FULL OUTER JOIN across three sources at the (channel, campaign_id, adset_id, ad_id) grain — for a workspace with 500+ campaigns × 5+ adsets × 5+ ads × 90 days, that's ~1.1M rows pre-aggregation. The Redis stale-while-revalidate cache layer absorbs the cost — first hit ~3-5s, subsequent hits ~80ms. The Refresh button triggers a forced re-fetch (throttled to 1 click / 10s).
Can I use the drill-down without connecting Shopify?
Yes. The page is source-additive — a pixel-only workspace sees the Pixel and Platform lenses (Shopify column hidden), the All lens collapses to best-of-two, and the page never breaks. Connecting Shopify unlocks the third lens and the True ROAS column on the same UI; nothing else changes. This is the same source-additive principle the rest of Admaxxer follows: 1 source = useful, more sources = better fidelity, missing sources = chip not break.