Visitor identity resolution is the process of deciding that several browsing sessions belong to the same person, and joining them into one record. It works by matching identifiers those sessions share — a first-party cookie id, a normalised email address or phone number, an ad click id, a payment customer id — and weighting each match by how much it actually proves.
Without it, one buyer looks like five strangers, and the sale gets credited to whichever session happened to be last.
This is the layer underneath every attribution report you read. If it is wrong, the report is wrong in a way that no choice of attribution model can fix.
Why does one customer show up as five different visitors?
Because a “visitor” in most tools is a cookie, not a person, and cookies do not last. Every time one is cleared, blocked, capped or never set, the same human comes back as somebody new. A single purchase can end up spread across four or five visitor records that nothing ever connects.
The usual causes, roughly in order of how much damage they do:
- Device switching. The ad gets clicked on a phone at lunch; the form gets filled on a laptop that evening. Two devices, two storage buckets, no shared identifier unless something deliberately creates one. This is the ordinary path for considered purchases, which is why tracking leads across devices is a separate problem from tracking sessions.
- Browser storage limits. Safari’s Intelligent Tracking Prevention (ITP) shortens the life of cookies written by JavaScript, so a visitor returning after a gap arrives as a stranger even though nothing was deleted by hand.
- Ad blockers and privacy extensions. A blocked script never writes an id at all. Those sessions are not missing — they are present, anonymous, and counted as new every time.
- Iframe embeds. When your form lives inside a Typeform, Jotform or GoHighLevel frame, the frame’s URL carries none of the campaign parameters from the parent page. The lead is real and the source is blank.
- Private windows and second browser profiles. A fresh storage bucket every time, by design.
- Long consideration windows. A trial that converts six weeks later has outlived the identifiers that would have explained where it came from.
Each of these is survivable on its own. Together they mean a meaningful share of your conversions land with no visible source, which is why ad conversions show up as Direct far more often than any media buyer’s instinct says they should.
What identifiers can you actually join people on?
Two kinds, and the difference between them is the whole discipline. Deterministic identifiers are ones the person supplied or a platform stamped: an email address, a phone number, an ad click id, a logged-in user id, a payment customer id. Probabilistic ones are inferred from context: an IP address plus a user agent, a device fingerprint, timing. Deterministic joins are safe to act on. Probabilistic joins need limits, because their failure mode is merging two different people into one customer.

Deterministic joins: the person handed you the key
A deterministic join is one you could defend in a meeting. The same normalised email appears on two sessions, so those sessions are the same person. The same works for a phone number in E.164 format, the same ad click id seen twice, or the same payment customer id on two orders.
Normalisation is where these quietly fail. Ava.Chen@gmail.com, ava.chen@gmail.com and avachen+forms@gmail.com are one mailbox and three different strings. 0412 345 678 and +61412345678 are one phone and two different strings. If you store what was typed instead of a canonical form, your deterministic joins silently stop firing and you will believe you have more customers than you do.
Probabilistic joins: useful, and worth a leash
IP address plus user agent is the workhorse here, and it is genuinely useful — the same device on the same network within a short window is usually the same person. It is also how a shared office network or a mobile carrier’s shared address turns twelve people into one.
Three rules keep this honest. Keep the time window narrow. Cap how many records a single weak signal may merge. And never let a probabilistic signal overrule a deterministic one — if the emails disagree, the emails win.
The expensive mistake is treating every signal as equal. A join on a hashed email and a join on “same office IP” are not the same claim, and a system that stores them the same way will eventually tell you that your entire Melbourne office is one very enthusiastic customer.
What does identity resolution change in your reports?
Four numbers move, and not all of them in the direction you were hoping for. Lead counts fall, because duplicates collapse. The Direct bucket shrinks, because sessions that had no source of their own inherit one. The measured sales cycle gets longer, because the real first touch is now further back. And first-touch and last-touch stop agreeing with each other.
That last one is the point. When both models return the same answer, one of them is not doing any work. The gap between “what started this” and “what closed this” is what tells you whether your top-of-funnel spend is doing anything.
Expect the deduplicated lead count to be the uncomfortable conversation. A CRM that counts lead rows and a system that counts unique contacts will disagree, and the smaller number is the correct one.
How do you attribute a session that has no source at all?
You look at the person’s other sessions. A conversion session arriving with no campaign parameters and no referrer is not evidence of Direct traffic — it is a missing value, and missing values can be filled in from neighbours.
The mechanism is inheritance. If the same person has another session close in time that carried a click id or campaign parameters, the conversion belongs to that click. The classic pattern: someone clicks an ad, lands, wanders off, comes back an hour later by typing the domain, and buys. The buying session looks Direct. The click that paid for it happened ninety minutes earlier on the same device.
Inheritance has to be time-boxed and evidence-ranked, or it becomes a machine for inventing attribution. A useful version inherits strong signals across a short window from sessions proven to be the same device, allows for a phone switching from wifi to mobile data mid-journey, and falls back to a referrer-only match only when nothing better exists.
The underlying principle is worth stating plainly: Direct should mean “we do not know”, not “a channel called Direct”. Anything that treats an absence of data as a traffic source is reporting on its own blind spot.
What can you fix without buying anything?
More than vendors admit. Five fixes, each with the limit that comes with it:
- Set your first-party cookie from the server. A
Set-Cookieheader from your own domain is not subject to the lifetime cap browsers apply to cookies written by JavaScript. Highest-value change on the list, and it does nothing for cross-device. - Normalise email and phone before you store them. Lowercase and trim email, strip Gmail dots and
+suffixes before hashing, convert phones to E.164. Cheap, permanent, and only helps once you have the contact details. - Capture the click id server-side from the landing URL.
fbclid,gclid,gbraid,wbraid,msclkidandttclidarrive in the URL, where no blocker can remove them. Requires you to have a server-side touch. - Pass identifiers explicitly across domains. If your landing page and checkout sit on different domains, the id has to travel in the URL — which breaks the moment a redirect or an email client rewrites the link.
- Deduplicate in your CRM on normalised email and phone. Worth doing regardless. Your CRM only ever sees people who submitted something.
The ceiling is the same for all five: they start working at submit time. Sessions where nobody submitted anything stay anonymous forever, and on a typical form those are the majority. Someone who typed an email and then abandoned is a partial lead — an identified person — but only if something captured the field before the page closed. If it did not, there is no identifier to resolve, and no amount of graph-building creates one.
How does PartialLeads resolve visitor identity?
PartialLeads maintains a per-tenant identity graph and resolves each person’s sessions with a cluster resolver that unions across six tiers: the durable visitor id, email, phone, IP plus user agent within a bounded window, a device fingerprint, and a shared click id seen on more than one session. Each tier carries its own confidence, and the strong tiers outrank the weak ones rather than averaging with them.
The visitor id itself is set by the server as a first-party cookie, with local storage as a backup, rather than written by the script in the browser — which is why Safari’s cap on script-written cookies does not quietly delete your returning visitors. The device-fingerprint tier is bounded by both a time window and a row cap, because an unbounded fingerprint tier is how false merges happen.
On top of the cluster sits visit-sibling inheritance, which heals attribution inside a single visit in three tiers: a strong inherit from a sibling session on the same client, IP and user agent within a short window; a “cellular handoff” variant that allows the IP to change when the user agent and Meta click cookie still match, covering a phone that swapped wifi for mobile data; and a weak referrer-only fallback when nothing stronger exists.
When a purchase arrives from Shopify, WooCommerce, Stripe, GoHighLevel or a webhook, it is matched back to a session on a ranked ladder — echoed visitor id first, then email, then phone, then IP — and both a first-touch and a last-touch row are written, snapshotting the campaign parameters, click ids, referrer and landing page as they were. Purchases that match nothing still land, and stay recoverable when identity arrives later.
Resolution also feeds dispatch. The identifiers on the cluster are what get hashed and sent to the ad platform through the Conversions API, and the completeness of that set is what the platform scores as event match quality. With email and phone as required form fields, PartialLeads consistently delivers EMQ 9+ — observed across customer accounts. Email-only forms cap structurally lower, because you cannot transmit a match parameter you never collected.

You can see the resolution working without opening anything. The Leads list renders each person’s touches left to right in a Journey column — a badge per session, a grey dot where the source is genuinely unknown, a green square for the conversion — with visual weight encoding signal strength and the ring treatment encoding how that session was stitched into the cluster. The Attribution report then shows first-touch, last-touch and a resolved model side by side, and itemises recovered attribution by mechanism.
| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| One person counted as five visitors | Six-tier cluster resolver with per-tier confidence | Leads list, unique contacts vs lead rows |
| Safari forgets the visitor after a short window | Server-set first-party visitor cookie, not script-written | Returning visitors stay stitched on the Journey |
| Ad clicked on a phone, purchase made on a desktop | Email, phone and payment-customer joins across devices | Journey timeline, Attribution report |
| Conversion session arrives with no source | Visit-sibling inheritance, three tiers, time-boxed | Recovered attribution, reclassified sessions |
| Purchase cannot be tied back to a click | Ranked purchase-to-session match ladder | Purchases ledger, matched vs unmatched |
| Click id lost to a blocked or malformed cookie | _fbc reconstructed from the fbclid in the URL |
CAPI activity log payloads |
| Weeks between the first click and the sale | First touch stored on the cluster, not the session | Time to purchase, first vs last touch |
| The ad platform cannot match your conversion | Full hashed identifier set sent server-side | Event match quality in Events Manager |
| Two form fills, two different email addresses | Phone and visitor-id joins merge the cluster | Leads list, unique contacts |
Two honest constraints. Identity resolution can only work with identifiers that were collected — it recovers the people who identified themselves somewhere, not the ones who never did. And matching on the platform side is not ours to control: PartialLeads controls what gets dispatched and how complete it is, while whether Meta or Google can resolve that person to an account depends on their graph, consent, and opt-out status. Any vendor claiming otherwise is describing the half of the problem they do not own.
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
- Meta for Developers — Conversions API, customer information parameters: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
- Meta for Developers — Conversions API, server event parameters: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/server-event
- Meta for Developers — Deduplicate Pixel and Conversions API events: https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events