Tracking & Attribution

How Do You Attribute WooCommerce Orders When the Checkout Is on Another Page?

WooCommerce orders read as Direct when the checkout sits on another page or domain. Why the hop drops click IDs, and how to attribute the order anyway.

Quick answer

You stop attributing on the session and start attributing on the identity. Click IDs and UTM tags only ever live in the landing URL, and the hop to a cart page, a subdomain checkout or a hosted payment page drops them — which is why the order reads as Direct. What survives the hop is the email and phone on the order itself. Store the ad source against a durable first-party visitor ID at the first pageview, then join the order back to that visitor by ID, email or phone after the order is written server-side.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

You stop attributing on the session and start attributing on the identity. Click IDs and UTM tags only ever live in the landing URL, and the hop to a cart, a subdomain checkout or a hosted payment page drops them. What survives is the email and phone on the order.

So the job is to store the source against a durable visitor ID at the first pageview, and join the order back to it afterwards.

Why do WooCommerce orders show as Direct when the checkout is on another page?

Because the thing carrying your attribution is the URL, and the URL changed. A buyer arrives on /offer?utm_source=meta&fbclid=…, clicks through to the cart, and the campaign tags are gone — nobody carried them. The order record that WooCommerce writes minutes later has no memory of them either.

Analytics then does the only thing it can. The checkout page’s referrer is your own site, which is not a traffic source, so the visit gets filed as Direct. This is the same failure mode behind almost every case of ad conversions showing up as Direct, just concentrated at one hop.

Two consequences follow, and both cost money. Paid channels look worse than they are, so you cut budget on campaigns that are working — attributing revenue back to the ad that started it is the whole job, and this hop is where it fails. And the ad platform, told nothing about the sale, keeps optimising against whatever partial signal it has.

What exactly breaks between the landing page and the checkout?

Four separate things, at four different stages. They fail independently, which is why fixing one rarely fixes the report.

  • The URL loses its parameters. Internal links do not carry utm_source or fbclid unless someone deliberately appends them. One click into the cart and the campaign context is gone from the address bar.
  • The referrer becomes your own host. A page-two referrer of yourstore.com tells a naive classifier nothing, so it falls back to Direct rather than preserving the original source.
  • The origin changes. A checkout on checkout.yourstore.com or on a separate domain does not automatically read cookies or localStorage written on the landing origin. Browser storage is scoped, and scope is the point of it.
  • The order is written by the server. The record WooCommerce creates contains a customer, line items and a total. It does not contain a click ID, because a click ID was never part of the order.

Four stage cards showing what a buyer's data carries at the landing page, cart, payment page and order, above an identity-tier panel in the PartialLeads dashboard style

Read that left to right and the shape of the problem is obvious. Everything the ad platform needs exists at stage one. Everything that identifies the human exists at stage four. Nothing carries all of it end to end unless you build the carry yourself.

Does a payment-gateway redirect lose your attribution too?

Yes, and worse than an internal hop. When the buyer leaves for PayPal, a hosted card page, a bank’s 3-D Secure challenge or a wallet, they are on an origin you do not control. Your tag cannot run there, your cookies are not readable there, and the return URL brings back a browser that may look new.

Three things commonly change across that round trip. The referrer is now the payment provider, which some tools cheerfully classify as a referral channel — an ad-driven sale credited to a gateway. The session may be treated as a fresh visit if it exceeds a timeout. And on mobile, the network can change while the buyer is away, so the IP you would have joined on no longer matches.

There is a structural fix for the sourceless return session: let it inherit from its sibling. If a session with no source shares a device and a short time window with a session that has strong attribution, the two are the same visit. That is the idea behind visit-sibling inheritance, and it is the only thing that reliably heals the return leg of a gateway redirect.

Why doesn’t a checkout on a subdomain share the landing page’s data?

Because browsers scope storage by origin, and a subdomain is a different origin. localStorage set on www.yourstore.com is not visible to checkout.yourstore.com. Cookies are the partial exception: a cookie written at the apex domain is readable on subdomains, but only if it was deliberately scoped that way when set.

That makes the cookie the more portable half of the pair, which matters because cookies are also the half under pressure. Safari’s Intelligent Tracking Prevention caps the lifetime of cookies written by JavaScript, so a script-set attribution cookie can expire well before a considered purchase completes. A cookie set by your server in the HTTP response is not subject to that same script-written cap.

If the checkout is on a genuinely separate domain rather than a subdomain, nothing is shared at all and you are in the same territory as attributing orders across two domains: the join has to happen server-side, on identity, because no browser mechanism spans it.

How do you carry attribution across the hop yourself?

Three approaches, in rough order of how well they hold up.

Persist at first touch, read at order time. On the first pageview, write the UTMs, click IDs, landing page and referrer into first-party storage keyed to a visitor ID you generate once. When the order form posts, read them back and attach them to the order as metadata. This is the standard build and it is sound — its limits are that storage is per-origin, clearable, and vulnerable to the ITP cap above if you write the cookie from script.

Decorate the links to the checkout. Append the visitor ID or the click IDs to internal links crossing to another origin. It works, and it is fragile: it breaks on any redirect you do not control, it does not survive the gateway return, and it puts identifiers into URLs that get pasted, shared and logged.

Rely on the platform’s own cookies plus a server-side event. Real, and necessary, but incomplete on its own. A server event sent without the platform’s browser cookie is an anonymous server event, and its match quality reflects that. Server-side delivery is how the event gets there reliably; it is not a substitute for what the browser collected. That distinction is the whole subject of sending WooCommerce purchases server-side.

All three are worth doing. None of them recovers a buyer whose storage was cleared between the ad click and the purchase, which is why the fourth approach is the one that changes the outcome.

Why is matching on identity more reliable than carrying the session?

Because identity is the one thing the order is guaranteed to have. A WooCommerce order cannot exist without a customer — an email at minimum, usually a phone and a billing address too. The browser state you were trying to carry is optional and destructible. The customer record is neither.

So invert the design. Instead of asking the browser to transport the ad source all the way to checkout, let the order arrive server-side and then ask a different question: which visitor does this person match? Normalise the email, normalise the phone to E.164, and look for a session that saw an ad and left the same identifiers behind.

That join is tiered, because the identifiers differ in strength. A visitor ID echoed back from your own storefront is near-certain. A normalised email is strong. A phone is strong. An IP plus user agent inside a narrow window is a reasonable last resort. Working down that ladder is what visitor identity resolution does, and it is what makes the checkout hop survivable: the hop breaks the session, but it does not break the person.

How does PartialLeads attribute WooCommerce orders when the checkout is elsewhere?

By capturing the source at the first pageview against a durable visitor ID, then matching the order to it server-side after WooCommerce writes it.

The capture side runs on the storefront. One tag records UTMs on the landing URL, falls back to the referrer when there are none, and stores the click IDs each platform uses — fbclid, gclid, gbraid, wbraid, msclkid, ttclid, epik — on the session. The visitor ID lives in localStorage with a first-party cookie backup, and that cookie is set by the server in the response rather than by script, so Safari’s cap on script-written cookies does not apply to it.

The order side runs one pipeline for every inbound purchase, whatever sent it — WooCommerce, Shopify, Stripe, GoHighLevel or a universal webhook:

  • Normalise, then deduplicate. Email and phone are normalised before anything else; the record is idempotent on source and order ID, so a redelivered webhook cannot create a second order.
  • Match by tier. A visitor ID echoed from the storefront first, then email, then phone, then IP. The order flips the lead to Completed with revenue attached.
  • Write both models. Every matched purchase produces a first-touch and a last-touch attribution row, snapshotting the UTMs, click IDs, referrer, landing page and time to purchase.
  • Heal the sourceless session. A session with no attribution inherits from a sibling on the same device within a short window — including the mobile case where the network changed and only the user agent and the Meta click cookie still line up.
  • Fan the conversion out. The matched purchase is dispatched server-side to Meta, Pinterest, TikTok and Google Ads, with a deterministic event ID so a retry cannot double-count.

PartialLeads purchases ledger showing matched and unmatched WooCommerce orders with resolved sources, above a recovered-attribution strip

You check it in three places. The Purchases ledger lists every order that arrived and whether it matched a session — the reconciliation that matters is that your WooCommerce admin order count equals the ledger’s. The Attribution report shows first touch, last touch and the resolved model side by side, with a recovered-attribution breakdown that itemises which orders moved off Direct and why. The Journey ribbon on the Leads list shows one buyer’s touches in order, so you can read a single order’s path without opening anything.

Two honest constraints. An order that matches nothing still lands, as an unmatched row, and stays recoverable if identity arrives later — it is not silently dropped, but it is also not attributed. And keep your client-side base pixels installed: a _fbc value can be rebuilt from an fbclid in the landing URL, but a _fbp cookie that was never set cannot be invented.

What breaks The mechanism Where you see it in the dashboard
Click IDs and UTMs dropped at the first internal hop Attribution captured on the first pageview and stored on the session, with referrer fallback Journey ribbon on the Leads list
The checkout session looks like a new, sourceless visitor Durable visitor ID in localStorage plus a server-set first-party cookie Journey ribbon — one person, N sessions
The payment page is on a domain you do not control The order arrives server-side and is matched on identity, not on the returning browser Purchases ledger — matched or unmatched
The returning session has no source at all Visit-sibling inheritance from a strong session in the same visit Attribution report — recovered attribution
The mobile network changed during the gateway round trip Cellular-handoff tier: same user agent and Meta click cookie in a short window, IP allowed to differ Journey ribbon
A redelivered order webhook counted twice Idempotent dedup on source and order ID, deterministic event ID on dispatch Purchases ledger, CAPI activity log
Refunds left inside attributed revenue Refund rows in the same pipeline, netted against paid Purchases ledger, net revenue

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources

https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters

https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc

https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events


Frequently asked questions

QWhy does my WooCommerce order say Direct when the buyer came from a Facebook ad?
Because the `fbclid` and UTM tags were in the landing URL only, and the buyer navigated away from that URL before ordering. The checkout page's referrer is your own site, which is not a traffic source, so the visit is classified as Direct. Nothing is broken in your pixel — the attribution was never carried from the landing page to the order.
QCan I just add the UTM parameters to my checkout link?
You can, and it helps for a simple internal hop. It stops helping the moment a redirect you do not control is involved, such as a payment gateway return URL, and it puts identifiers into addresses that get shared and logged. Treat link decoration as a supplement to storing attribution at first touch, never as the primary mechanism.
QDoes my checkout need the tracking tag installed on it?
It helps a great deal if the checkout is on your own domain, because the visitor ID echoes back and gives you the strongest possible match. It is impossible on a hosted payment page owned by the provider. That is exactly why the order should be matched server-side on email and phone rather than depending on a script running at checkout.
QWhat if the buyer checks out with a different email than the one they used earlier?
Then the email tier fails and the match falls through to the next identifier — phone, or the visitor ID if the same browser is still in play. If nothing matches, the order still lands as an unmatched purchase rather than disappearing, and it can be resolved later when another signal ties those identities together.
QMy checkout is on a subdomain. Why doesn't it see the landing page's data?
Browsers scope storage by origin, and a subdomain is a different origin, so `localStorage` does not cross. Cookies can, but only if they were explicitly scoped to the apex domain when set. If the checkout is on a completely separate domain, no browser storage is shared at all and the join must happen server-side.
QDoes a PayPal or 3-D Secure redirect break attribution permanently?
Not permanently, but it does break the session. The buyer returns on a URL with no campaign tags, sometimes on a changed IP, and the returning session has no source of its own. Recovering it means either inheriting attribution from the earlier session on the same device, or matching the finished order to that session by email or phone.
QWill fixing this change what Meta or Google report?
It changes what they can report, which is not the same thing. Matching the order to a click lets you send a conversion carrying real identifiers instead of an anonymous one. Whether the platform then resolves that event to a user depends on its own graph, consent and opt-outs. You control the dispatch and the identifiers; you do not control the match.
QDo I still need the Meta Pixel on the store if orders are matched server-side?
Yes, in most cases. The base pixel sets the `_fbp` cookie, and that cookie is a match parameter your server events want to carry. A `_fbc` value can be rebuilt from an `fbclid` in the landing URL, but a cookie that was never set cannot be synthesised. Server-side is the reliable delivery path, not a replacement for browser-side collection.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.