A form on a subdomain breaks tracking because the browser treats forms.example.com and www.example.com as two different storage buckets. Your visitor ID, UTMs and click ID were written on the first host. The form runs on the second and sees none of them. Fixing it means carrying identity across that hop — with a domain-scoped cookie, decorated links, or server-side stitching.
The symptom is always the same. The ad platform shows clicks, the CRM shows leads, nothing connects them, and every lead that started on a paid click arrives labelled Direct.
Why Does a Subdomain Count as a Different Site?
Because the browser scopes storage by origin, and a subdomain is a different origin. www.example.com and go.example.com share a registrable domain but not an origin, so anything keyed to the origin does not travel between them.
That splits your setup in two halves:
localStorageandsessionStorageare origin-scoped. Nothing written on www is readable on go, and no setting changes that. Most tracking scripts keep their visitor ID here first, which is why it is simply absent on the second host.- Cookies are scoped by the
Domainattribute, not the origin. A cookie written without aDomainis host-only; one written withDomain=example.comis sent to the apex and every subdomain under it. This is the one piece of state that can cross the hop.
Set-Cookie: pl_vid=abc123; Path=/ # host-only: www can read it, go cannot
Set-Cookie: pl_vid=abc123; Domain=example.com; Path=/ # readable on www, go, app, shop
One attribute is the difference between a continuous journey and two anonymous strangers. Nothing errors, so most teams never look — the second host just quietly starts a new visitor.
Which Setups Hit This Hardest?
Three patterns, most common first:
The funnel subdomain. Ads point at www.example.com, the “Get a quote” button sends the visitor to go.example.com where a funnel builder hosts the form. Two hosts, two sessions, one human.
The CNAME’d SaaS host. You point forms.example.com at a form, booking or funnel vendor. The page is on your domain but served and instrumented by someone else’s infrastructure, and their script sets their cookies on their terms.
The app or store subdomain. Marketing on www, signup on app.example.com or checkout on shop.example.com. The lead or the order is created on a host that never saw the ad click.
Why Does the Lead Show Up as Direct Instead of the Ad?
Because the form host sees neither the UTMs nor a useful referrer. The UTM parameters were on the landing page URL and were not carried across the link, and document.referrer on the form page is your own www.example.com — a self-referral, which is not a traffic source.
Left with nothing, most tools do the only thing they can: call it Direct. It is one of the most common reasons ad conversions show as Direct, and on subdomain setups it is structural rather than intermittent — every lead, every time.
The click ID disappears the same way. fbclid, gclid, gbraid, wbraid, msclkid and epik all arrive as URL parameters on the landing page. If the hop does not carry them and the platform’s own cookie is host-only, the form host has no click reference to attach. The conversion you send back later is a server event with no click ID, and match quality falls accordingly.
What Does the Subdomain Hop Actually Cost You?
Three things, and they compound.
Your channel report inverts. Paid is undercounted by exactly what Direct gains, so Direct looks like your best channel while it absorbs everything you paid for. Budget decisions made on that report move money the wrong way.
Your ad platform stops learning. Conversions sent without a click ID or a matched user are weaker optimisation signal, and no amount of budget fixes a signal problem.
Sales loses context. The lead arrives with a name, an email and no story — no campaign, no landing page, no visit count.
None of it shows up as an error. That is what makes it expensive: the dashboards are full, the numbers are wrong, and nothing is red.
How Do You Fix Cross-Subdomain Tracking Without a Vendor?
Four approaches, each with a real limit. Most setups need two of them.
Scope the visitor cookie to the apex domain
Write your first-party visitor cookie with Domain=example.com instead of leaving it host-only, and both hosts read the same ID — the cleanest fix when you control both.
The limits are worth knowing. It only works within one registrable domain — example.com and examplepages.com are still separate worlds, and so is a form on a vendor’s own domain. It does nothing for localStorage. And on Safari a cookie written by JavaScript is capped at seven days, so a returning visitor beyond that window is new again even with correct scoping. A cookie set by your server in an HTTP response is not subject to that cap, which is why the server-set version is the durable one — the same principle behind first-party attribution generally.
Carry the parameters in the link
When you send the visitor from www to the form host, append what matters: UTMs and whichever click ID is present. Google Analytics’ cross-domain linker does this for its own session; you can do it yourself for everything else.
const keep = ['utm_source','utm_medium','utm_campaign','utm_content','utm_term',
'gclid','gbraid','wbraid','fbclid','msclkid','ttclid','epik'];
const here = new URLSearchParams(location.search);
document.querySelectorAll('a[href*="go.example.com"]').forEach(a => {
const url = new URL(a.href);
keep.forEach(k => { if (here.has(k)) url.searchParams.set(k, here.get(k)); });
a.href = url.toString();
});
The limit: it only covers visitors who arrive by clicking that link, in that session. Bookmarks, a typed URL, an email link straight to the form, a redirect that strips the query string — all arrive bare. It fixes the common path and nothing else.
Serve the form on a path instead of a subdomain
example.com/quote has none of these problems because it is the same origin. If your form tool supports a reverse proxy or a subdirectory install, this removes the hop rather than patching it — the most reliable option, and usually the one you cannot have.
Join the two sessions after the fact
If the browser cannot carry state, the server can reconstruct it. Two sessions that share an email, a phone number, a device or an IP and user agent within a short window are probably one person. This is the only approach that also works when storage was cleared, the visitor switched devices, or the form host is a vendor domain you do not control — it is how visitor identity resolution works in practice.
The limit is honesty about confidence. An email match is deterministic. An IP-and-user-agent match on a corporate network or a shared carrier NAT is a probability, and any system doing this should score those tiers differently rather than merging everything it can.
How Does PartialLeads Track Leads Across Subdomains?
By not depending on browser storage surviving the hop. The same tag goes on both hosts under the same client key, both appear in your authorized domains, and the sessions are joined on the server.

Three mechanisms do the work.
A server-set visitor ID. The pl_vid cookie is written by the server in the HTTP response rather than by document.cookie, with a one-year lifetime and a localStorage copy as backup. Because it is not script-written, Safari’s seven-day cap does not apply to it. Across a subdomain hop it still helps only as far as the cookie’s scope reaches — the first mechanism, not the only one.
A six-tier identity cluster. The resolver unions a person’s sessions by visitor ID, email, phone, IP plus user agent within a window, a device fingerprint hash with a 90-day window, and click ID — a gclid or fbclid seen on more than one session. Each tier carries its own confidence, and the fingerprint tier is capped at 200 rows so a common device signature cannot merge a crowd. The subdomain hop is what the lower tiers exist for: two sessions, same device, same user agent, usually the same IP, seconds apart.
Visit-sibling attribution inheritance. This is the one that fixes the Direct problem specifically. When a session arrives with no attribution and a sibling session from the same client, IP and user agent exists within ±30 minutes, the bare session inherits the strong attribution — UTMs and click IDs — from its sibling. A second tier covers the cellular handoff: same user agent and a matching _fbc cookie within ±10 minutes, IP allowed to differ, for a phone dropping off wifi mid-flow. Visit-sibling inheritance is a general mechanism, and the subdomain hop is its most common trigger.
Two smaller pieces matter here. UTM capture falls back to document.referrer, and when the subdomain form is itself an iframe, attribution is relayed into the frame by postMessage. And because fields are captured on input with a terminal flush before the page unloads, a visitor who types an email and never submits still produces a partial lead with a deterministic identifier to join on. The abandoned form repairs the attribution.

The proving surface is the Journey ribbon on the Leads list. Each touch renders as a badge left to right, so a row reads as a sentence — ad click, then the form host, then the outcome — without opening the record. The ring treatment on each badge encodes how that session was stitched into the person: whether the join came from the visitor ID, the email, or the IP-and-user-agent tier. In the Attribution report the same repairs appear in aggregate under recovered attribution, where sessions that carried no utm_source but resolved to a real channel are counted separately. That bucket is typically the largest one, and a subdomain hop reliably fills it.
Two honest constraints. The lower tiers are probabilistic — a shared office IP is weaker evidence than an email, and it is scored that way. And the strongest join is still a contact detail typed into the form, which is why capturing email and phone before submit does more here than any cookie setting. If your form lives on a genuinely separate domain, the same machinery applies with the cookie tier gone entirely — the two-domain case leans harder on the server-side joins.
| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| Visitor ID doesn’t carry from www to the form host | Six-tier identity cluster: visitor ID, email, phone, IP + user agent, device fingerprint, click ID | Journey ribbon, ring treatment showing which tier stitched it |
| The form session arrives with no UTMs or click ID | Visit-sibling inheritance: same client, IP and user agent within ±30 minutes inherits the attribution | Journey timeline, source on the converting touch |
| The lead reads as Direct because the referrer is your own host | Referrer fallback, channel resolved on the cluster rather than the session | Attribution report, recovered attribution |
| Safari drops the visitor a week later | Visitor cookie set by the server, not by script, so the seven-day cap doesn’t apply | Returning visitors staying on one journey |
| The subdomain form is an iframe and sees no UTMs | postMessage attribution relay from the parent frame |
Correct source label on the lead |
| Nobody submits, so there is no contact detail to join on | Field capture on input, terminal flush before the page unloads | Leads list, Partial badge |
| The purchase happens later on a third host | Purchase matched by visitor ID, then email, then phone | Purchases ledger, revenue on the lead |
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
- https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
- https://developers.facebook.com/docs/meta-pixel/implementation/conversion-tracking