If nearly every order in your GA4 shows up as Direct or (not set) while you're spending real money on Meta and Google — you're not imagining it, and you didn't misconfigure a tag. On a headless Shopify store this is structural. It hits close to 100% of orders, not a few. And it quietly taxes every ad dollar you spend.
Here's exactly why it happens, how to confirm it on your own store in about fifteen minutes with tools you already have, and how to think about what it's costing you.
Why headless breaks attribution (the short version)
A standard Shopify theme and its checkout live on the same domain and share the same browser cookies. The visitor's session — including which ad or campaign brought them — travels cleanly from the first pageview through to the "thank you" page. Attribution just works.
Going headless splits that in two. Your storefront runs on a custom front-end (Hydrogen, Next.js, Remix, whatever) on your domain, and Shopify's checkout runs on checkout.shopify.com or a *.myshopify.com surface. These are separate contexts that don't share cookies. When the shopper crosses from your storefront into checkout, the session identifiers — the client ID, the campaign attribution, the UTM values — don't make the hand-off. The purchase gets recorded with no memory of where the visitor came from.
The tell that it's structural, not a settings bugIt affects essentially all of your orders, consistently — not an odd 10% here and there. A tagging mistake is patchy. A broken cookie hand-off is total. If your "unassigned / direct" share is up near 100% of paid-era orders, that's the signature.
Because it's structural, you can't fix it inside GA4 settings or the Shopify admin. It has to be fixed in code, on both the tracking paths that headless breaks — the conversion events and the marketing-source capture.
Confirm it on your own store in ~15 minutes
Don't take anyone's word for it — including mine. Run these checks yourself. They use GA4, your Shopify orders export, and the Meta Ads Manager you already have.
Check 1 — GA4: where are your purchases attributed?
- Open GA4 → Reports → Acquisition → Traffic acquisition. Set the date range to a period where you were actively running ads.Look at the Session default channel group rows.
- Add "Purchases" or "Conversions" as the metric and sort by it. Now read what channel your purchases are credited to — not sessions, purchases.If Direct and Unassigned dominate your purchase rows while Paid Social / Paid Search sit near zero, that's the leak.
- Cross-check in Explore. Build a free-form exploration with dimension
Session source / mediumand metricPurchases.A healthy store shows spend-bearing sources (facebook / cpc,google / cpc) carrying purchases. A broken headless store shows(direct) / (none)and(not set)holding the vast majority.
Check 2 — Shopify: is the marketing source actually landing on the order?
- Export your orders (Orders → Export → current view, CSV) for a window where you were advertising.Open the CSV and find the marketing / referring columns.
- Scan the UTM fields. On many headless setups these arrive empty on nearly every row — no
utm_source, noutm_campaign— even for orders you know came from a paid click.Blank marketing source on paid-era orders = the checkout never received the campaign data. - Look at Analytics → Reports → Sales by traffic referrer inside Shopify itself.If Shopify's own conversion summary shows almost everything as Direct, the platform confirms it independently of GA4.
Check 3 — Meta / Google: does the platform see your conversions?
- Meta Events Manager → your pixel/dataset → Overview. Compare Purchase events the platform recorded against the orders Shopify actually processed in the same window.A large shortfall (Meta sees far fewer purchases than really happened) means the signal is being lost — and the algorithm is optimizing on a fraction of the truth.
- Check event match quality / deduplication. If you're firing browser-side only, iOS and ad-blockers erase a chunk of it; if you added server-side without dedup, you may be double-counting.Either way the number the platform optimizes against isn't your real number.
- Google Ads → Conversions. Look for "no recent conversions" warnings or counts that don't reconcile with Shopify sales.Enhanced Conversions being off or unfed is the headless-side symptom here.
If two of these three light up, you have the structural problem. It's extremely common on headless Plus builds — the front-end team ships a beautiful storefront, and tracking, which lives in the seam between storefront and checkout, is the thing that silently falls through.
What it's actually costing you
The lost dashboards are annoying, but they're not the real cost. The real cost is that your ad platforms optimize toward whoever they think converted. When conversions come back incomplete or mis-credited, Meta's and Google's algorithms steer budget toward the wrong audiences and pause the wrong campaigns. A slice of every dollar goes to reaching people who were never going to buy — not because your targeting is bad, but because the machine is learning from bad data.
You also lose the ability to make good manual calls. If utm_campaign is blank on every order, you can't tell which creative or campaign actually drove revenue — so you can't confidently cut the losers or pour money into the winners. You're scaling blind.
A rough way to size itPost-iOS signal loss commonly runs in the low double digits as a share of spend. As an illustration, at $20K/mo in ad spend, a conservative 15% mis-optimization rate is roughly $3,000/mo — about $36K a year spent against the wrong data. Your real figure depends on your channels and current setup; the point is that it's a recurring leak, not a rounding error. There's a slider on the Shopwright homepage to try your own number.
How the fix works (high level)
Fixing it means rebuilding the hand-off that headless removed, on both paths, and then proving it works:
- Server-side conversion events. Send purchases from a trusted server context — Shopify webhook → Meta Conversions API, Google Ads Enhanced Conversions, GA4 Measurement Protocol, TikTok Events API. This is ad-blocker- and iOS-proof, so the platforms see close to 100% of real conversions.
- Client-side pixel, deduplicated. Keep a browser-side signal for audience/behavioral data, but tie it to the server events with a shared event ID so each sale is counted exactly once across both paths — never doubled.
- Marketing-source capture. Carry
utm_source / utm_campaign / ad / placementacross the storefront-to-checkout boundary so real campaign data lands on every order, visible in Shopify's own reports and export. - Live verification. The part that matters: click a real ad, place a real test order, and watch the campaign attach to that order and the conversion appear in each platform. You see it work before it's called done.
Done right, this is a one-time fix that lives in your own codebase — not another monthly tracking-SaaS subscription you rent forever.
Frequently asked questions
Why does my headless Shopify store show all orders as Direct or (not set)?
Because a headless storefront and Shopify's checkout are separate surfaces that don't share browser cookies. When a shopper crosses from your custom front-end into checkout, the session and campaign data don't make the hand-off, so the purchase is recorded with no source. On headless it affects close to 100% of orders, which is the sign it's structural rather than a tagging mistake.
Can I fix headless attribution in GA4 or Shopify settings?
No. It's not a configuration toggle — the data is lost at the boundary between the storefront and checkout, before any setting can act on it. The fix has to happen in code, on both the conversion-event path (server-side + deduplicated client-side) and the marketing-source capture path.
How do I know if my headless store has broken attribution?
Run three checks: in GA4, see whether your purchases are credited to Direct/Unassigned instead of Paid Social/Paid Search; in a Shopify orders export, see whether utm_source and utm_campaign are blank on paid-era orders; and in Meta Events Manager, compare recorded Purchase events against actual Shopify orders. If two of the three light up, you have the structural problem.
Does server-side tracking (Conversions API) alone fix it?
Partly. Server-side events restore conversion coverage, but you also need event-ID deduplication so client-side and server-side don't double-count, and separate marketing-source capture so real campaign data lands on each order. A server-side-only setup often fixes the count while leaving UTMs blank.
How much does a headless Shopify attribution fix cost?
A fixed-scope Attribution Fix — full audit, both-sided conversion tracking, marketing-source capture, and live test-order verification — runs $5,000–$10,000 one-time, quoted after a free scoping call. For comparison, a full headless rebuild runs $80K+, and rented tracking SaaS bills $150–500/month indefinitely; the fix is one-time and lives in your own codebase.
Want to know exactly what your store is losing?
Send your store URL and rough monthly ad spend and I'll do a free 20-minute look — a straight read on whether attribution is broken and roughly what it's costing. No deck, no pitch.
Book a free 20-min look →or email contact@shopwright.io