Tracking & Attribution

How Do You Track Leads When Your Form Is on a Subdomain?

Leads from a form on a subdomain arrive as Direct because the browser treats it as a separate site. Here's why, and the three ways to fix the hop.

Quick answer

A form on a subdomain breaks tracking because the browser treats `forms.example.com` and `www.example.com` as separate storage. The visitor ID, UTMs and click ID were written on the first host; the form runs on the second and sees none of them, so a paid lead arrives labelled Direct. You have three honest fixes: scope the visitor cookie to the apex domain so both hosts can read it, carry the parameters in the link when you send the visitor across, or stitch the two sessions server-side using the identifiers they share — same device, same IP, same email, same click ID.

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.

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:

  • localStorage and sessionStorage are 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 Domain attribute, not the origin. A cookie written without a Domain is host-only; one written with Domain=example.com is 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.

Diagram of two browser sessions on www and go subdomains being joined into one identity cluster by the six identity tiers, shown as a dark PartialLeads dashboard mockup

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.

PartialLeads Leads list showing journey ribbons that run from an ad touch through the go subdomain to a Partial badge or a completed purchase

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


Frequently asked questions

QDo cookies work across subdomains?
Only if the cookie is written with a `Domain` attribute. A cookie set without one is host-only, so `www.example.com` can read it and `go.example.com` cannot. Set `Domain=example.com` and the cookie is sent to the apex and every subdomain under it. `localStorage` is a different story — it is scoped to the origin and never shared between subdomains, whatever you configure.
QWhy do leads from my funnel subdomain show as Direct?
Because the form host sees no UTMs and a referrer that points at your own site. The UTM parameters were on the landing page URL and were not carried across the link, and a self-referral is not a traffic source, so the tool records the only thing left: Direct. It is structural, not intermittent — it happens to every lead from that path.
QDoes a subdomain need its own tracking pixel?
It needs the same tag, not a different one. Installing a second pixel with a different ID creates two separate datasets and makes the split worse. Put the same tag and the same client key on both hosts so the sessions land in one place, then the stitching happens server-side rather than depending on the browser carrying state.
QWill Google Analytics cross-domain tracking fix this?
Partly. The cross-domain linker passes a client ID in the URL so Analytics treats the hop as one session, which fixes your Analytics reports. It does nothing for your CRM, your ad platform's click ID, or any conversion you send server-side, because those systems never see that parameter. It fixes one report, not the attribution underneath it.
QShould I move my form to a path instead of a subdomain?
If you can, yes — `example.com/quote` is the same origin and the problem disappears rather than being patched. In practice most people cannot, because the form lives on a funnel builder, booking tool or CRM that only offers a subdomain or its own domain. When the path is not available, carrying parameters in the link plus server-side identity joins is the working alternative.
QDoes this affect Meta and Google click IDs too?
Yes, and it is the expensive part. `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 cookie is host-only, the form host has no click reference to attach. The conversion still sends, but it sends without the identifier the platform matches on.
QCan you fix attribution for leads that already came in wrong?
Sometimes, if the sessions were recorded. A join on an email or phone that appears on both sides is deterministic and can be re-resolved later. What cannot be recovered is a click ID that was never captured on any session — if it was dropped at the hop and nothing stored it, there is nothing to rebuild from. Fix the capture first, then reprocess.
QDoes an IP and user-agent match risk merging two different people?
It can, which is why it should be a low-confidence tier rather than a primary join. Office networks and carrier NAT put many people behind one address. A reasonable system scores that tier below email, phone and visitor ID, bounds how many rows a single device signature can pull in, and prefers a deterministic identifier whenever one exists.
QMy form subdomain is hosted by a SaaS vendor — can I still track it?
Usually yes, if the vendor lets you add a script to the page or embed the form on a page you control. What you cannot do is change how their own cookies are scoped. That is the case where server-side identity joins matter most, because the browser-side fix — apex cookie scoping — is not yours to make.

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.