The signal families, matching lanes, confidence bands, and hard refusals inside ClickStream's person-level identity resolution engine.
An identity graph turns scattered device and session observations into person-level records. This whitepaper documents how ClickStream's shipped identity graph architecture does it: the seven signal families it ingests (first-party cookie IDs, hashed emails and phones, device fingerprints and mobile advertising IDs, thirteen ad-click ID types, OAuth login subjects, CRM identifiers, and environmental context), the deterministic and probabilistic matching lanes that connect them, the canonical-node merge pipeline, and the three-band confidence model that makes resolution quality a product primitive. We spend as much time on what the graph refuses to merge as on what it merges, because the dominant failure mode in identity resolution is over-merging. We close with per-tenant HMAC isolation, bot quarantine, and tenant-scoped GDPR erasure. No match rates or coverage percentages appear in this paper, deliberately.
Every downstream promise — accurate people counts, cross-device journeys, CRM sync you can trust — is only as good as the merge decisions underneath it. A graph that merges too eagerly builds "Frankenstein" profiles that blend housemates, coworkers, and strangers who clicked the same ad; one that never merges leaves you with device counts dressed up as people. This paper shows exactly where ClickStream draws the line, so you can hold any identity vendor — including us — to the same standard: ask not just how they match, but what they refuse to match.
Identity resolution marketing is a bidding war of match rates, and match rates reward exactly the wrong behavior: every additional merge — right or wrong — raises the number. The cheap way to a spectacular match rate is a graph that fuses two housemates on a shared Wi-Fi network, or everyone who clicked the same forwarded campaign link, into one "person." The resulting profile is worse than two anonymous ones: it poisons personalization, corrupts attribution, and, once synced to a CRM, is genuinely hard to unwind.
ClickStream's identity graph architecture takes the opposite premise: a merge is a strong claim that two observation histories belong to one human, so the burden of proof sits on the merge, not on the refusal. Deterministic evidence merges immediately; probabilistic evidence must clear explicit gates; an entire class of superficially plausible matches is refused outright and recorded as refused. The rest of this paper walks the machinery — and the hard stops.
Every identity signal that reaches the graph belongs to one of seven families, and the family determines how much the graph is allowed to trust it:
| Family | Identifiers | Role in Resolution |
|---|---|---|
| First-party cookie IDs | visitor_id, session_id, clickstream_id | Deterministic per browser; probabilistic per person |
| Hashed email & phone | SHA-256 email hash (HEM), hashed phone, per-tenant HMAC variants | Deterministic person keys |
| Device & MAID | Device fingerprint (with its own confidence score), IDFA / GAID | Probabilistic device inference |
| Ad-click IDs (13 types) | gclid, fbclid, msclkid, ttclid, twclid, sccid, epik, irclickid, _kx, dclid, gbraid, wbraid, li_fat_id | Probabilistic, TTL-bounded bridges |
| OAuth login subjects | Google, Facebook, Apple, LinkedIn subjects — per-tenant HMAC'd, provider-domain-separated | Deterministic authenticated keys |
| CRM identifiers | customer_id, account_id | Deterministic first-party keys |
| Environment & behavior | IP-derived household and workplace clusters, geohash, timezone, behavioral similarity | Context only — never person keys on their own |
Two families deserve a note. First-party cookie IDs are the workhorse of session continuity — living on your own domain, they persist up to the browser-enforced ceiling of roughly 400 days rather than being purged in days like third-party cookies (see First-Party Cookies Are the Gold Standard for Identity). And OAuth subjects are the quiet star: a visitor signing in with Google or Apple yields a stable, provider-verified subject that survives cookie clearing entirely — the pattern behind Social Login as Identity Infrastructure. Environmental signals, by contrast, can never anchor a person on their own; they corroborate, and they power the separate household and workplace clustering layer.
Deterministic matching is exact-match on person-level keys: the same hashed email, hashed phone, CRM identifier, or OAuth subject on two visitor records means the same person, full stop. When such a signal arrives, the graph looks up every node carrying the same value and connects them with maximum-confidence edges. Deterministic signals never expire — an email hash is as identifying in a year as today — and each repeated co-occurrence increments the edge's observation count rather than creating duplicates.
The lane also matches through history: the graph keeps every hashed email a visitor record has ever carried, so a person who identified as alice@old-employer last year and alice@new-employer today still resolves to one node the moment either address reappears. Internally, each node stores its deterministic identifiers directly on the row for fast lookup, every non-null signal is upserted into an append-only store deduplicated per node, type, value, and tenant, and a materialized person profile is rebuilt from that store on every ingestion — the profile your dashboard shows is a deterministic function of the underlying evidence, never a cached guess.
Not every deterministic key carries equal weight. The graph's per-signal confidence weights encode a trust hierarchy:
| Signal Type | Node Confidence Weight | Anchor Class |
|---|---|---|
| Hashed email (HEM / tenant-HMAC HEM) | 0.30 | Deterministic |
| OAuth subject (Google, Facebook, Apple, LinkedIn) | 0.25 | Deterministic |
Hashed phone / CRM customer_id | 0.20 | Deterministic |
| Mobile advertising ID (IDFA / GAID) | 0.10 | Probabilistic |
| Device fingerprint | 0.08 | Probabilistic |
| Visitor / ClickStream cookie ID | 0.05 | Observed |
An authenticated login subject outranks a CRM ID (logins are verified by the provider; CRM IDs are asserted by an import), and both outrank anything inferred from a device. That ordering matters most in section 6, where the strongest anchor type on a node sets its confidence band.
The probabilistic lane exists for the visitor who browses your site on a phone over lunch and returns on a laptop that evening without ever logging in. No deterministic key connects the two devices, so the graph reasons from circumstantial evidence — carefully.
Candidate pairs are drawn from a 30-day window — people switch devices over days and weeks, not hours — and only among still-anonymous records: once either record carries a deterministic key, the deterministic lane owns it. Pairs are scored by stacking weighted evidence: a shared residential IP is strong (a home network serves a handful of real people), the same campaign click landing on both devices is strong, matching timezone and language add a little, and a public or heavily shared IP contributes almost nothing. Hard physics checks run first — simultaneous activity from different networks disqualifies a pair outright.
The output is gated twice. First, no pair scoring below 0.60 composite confidence is even considered a candidate — below that line the evidence is noise and no hypothesis is recorded. Second, clearing 0.60 earns consideration, not a merge: automatic merging additionally requires at least one genuinely person-scoped signal (section 7). Pairs in between become review candidates that future evidence can promote or retire.
Probabilistic evidence also ages out. Unlike deterministic keys, probabilistic edges carry TTLs matched to how long each signal plausibly stays person-stable:
| Edge Type | TTL |
|---|---|
| Session ID edges | 7 days |
| Visitor ID / device fingerprint edges | 14 days |
| Geolocation (geohash) edges | 14 days |
| MAID and household edges | 30 days |
| Workplace edges | 60 days |
| Ad-click ID edges | 90 days (matching attribution windows) |
| Deterministic edges (HEM, phone, CRM, OAuth) | Never expire |
Expired edges are pruned on a maintenance schedule (section 10), so a stale inference cannot silently anchor a merge months after the evidence went cold. Why cross-device resolution got harder as third-party cookies collapsed is the subject of our companion paper on cross-device identity resolution.
When an ingested signal reveals that two existing nodes describe the same person, the graph merges them through a three-step pipeline:
Absorbed nodes are not deleted; they become redirects. Any lookup landing on an absorbed node follows the canonical chain to the surviving person — traversal is cycle-safe and capped at depth 10 — so historical references, stored events, and old CRM syncs keep resolving correctly forever. Merge history makes every consolidation auditable: for any person record, you can reconstruct which observation histories were joined, when, and on what evidence. That auditability is what makes the refusals in section 7 enforceable, and what lets the compliant anonymous-visitor-to-CRM-contact path promote a person with a defensible provenance trail behind it.
Most identity vendors keep resolution confidence internal. ClickStream promotes it to a customer-facing primitive: every person node carries a discrete confidence band — deterministic, probabilistic, or observed — set by the strongest first-party anchor the node holds:
deterministic — at least one first-party PII, login, or CRM key (hashed email or phone, OAuth subject, customer ID). The graph knows the person; safe to activate on.probabilistic — no first-party person key, but device-graph inference (MAID or fingerprint), or a person key asserted only by an enrichment partner and never observed first-party. Partner assertions deliberately cannot reach the deterministic band.observed — a single-device observation history: cookie IDs, click IDs, geography. Real, useful, not yet a person.Alongside the band, each node carries a scalar confidence score built from the weight table in section 3: a base of 0.1 plus the weight of each distinct signal type present. Two mechanics keep the scalar honest. A deterministic-anchor floor lifts any deterministically anchored node to at least 0.72 — a lone hashed-email node would otherwise sum to a misleading 0.40 despite the person being known — and each additional distinct deterministic anchor type adds 0.07 as independent corroboration, capped at +0.20. Recency decay multiplies the score by 0.9 after 30 days of inactivity and 0.8 after 60, so a dormant profile reports its staleness. Partner-supplied signals are further discounted: their stored confidence is clamped below first-party equivalents and their contribution to the node score is reduced by a fixed ratio — bought data can inform a profile but never impersonate first-party evidence.
The band travels with every graph response, which turns it into a filter you can build policy on: activate campaigns on deterministic-band people only, personalize gently on probabilistic, simply measure the observed band. Resolution quality stops being a vendor's private opinion and becomes a field in your data.
This section is the heart of the architecture. Each refusal below is a shipped rule, most of them learned or hardened through production forensics:
| Refusal | Trigger | Outcome |
|---|---|---|
| Conflicting identifiers | Two nodes hold different, non-overlapping values for the same deterministic key | Persistent merge block recorded |
| Shared mailbox | One email hash fans out across more than 5 distinct visitor records | Auto-merge refused entirely |
| Household environment | Only environmental / cohort signals matched | Review link at most, never auto-merge |
| Uncorroborated MAID conflict | Disjoint advertising IDs without stronger shared evidence | Merge blocked |
| Physics violation | Simultaneous activity from different networks | Hard block |
| Fingerprint collision | Same fingerprint active simultaneously on separate records | Fingerprint quarantined |
| Forwarded link | One click-ID value appearing across more than 2 other nodes | Value skipped as a bridge |
| Consent fail-safe | Either node carries an opted-out or suppressed identifier | Merge refused |
If two nodes carry different customer IDs, different hashed emails, or different OAuth subjects for the same provider, they are different people — no volume of soft evidence outweighs that. The graph does not merely skip the merge; it writes a persistent merge block recording the conflicting signal type and values, so no future weak trigger can quietly retry what a hard contradiction already ruled out.
An email address observed across many distinct visitor records is usually not one person — it is info@, sales@, a family account, or a form-default address. When a single email hash fans out across more than five distinct visitor records, the graph refuses email-based auto-consolidation for that value entirely rather than routing it to review. Role mailboxes must never become a person.
An entire class of signals — shared residential IP, matching timezone, language, screen resolution, browser version, device-model class, concurrent browsing from the same network, even arrival via the same marketing campaign — is formally designated environmental: every member of a household satisfies all of them without being the same person. The rule: a cross-device auto-merge requires at least one signal outside the environmental set — evidence tying two devices to one human rather than to one Wi-Fi network or one campaign's audience.
Behavioral similarity is deliberately classified as non-distinguishing too. An internal forensic review in July 2026 found that a prior policy counting behavioral-similarity tokens as person-scoped had auto-merged 24,254 cross-device pairs in one week with zero identity anchors among them — on narrow consumer funnels, two strangers browsing the same popular pages simply look alike. The policy was reversed, the pairs unmerged; behavioral evidence now creates review links only. We publish the number because it is the strongest argument in this paper: forensic reviews and unmerges are what an identity graph's integrity actually costs.
Two nodes with different mobile advertising IDs would normally block, but two legitimate exceptions exist — one person owning an iPhone (IDFA) and an Android device (GAID), and a single device that reset its advertising ID, yielding two disjoint MAIDs for one handset — and the graph demands proof for both. The merge proceeds only when the pair shares a stronger deterministic key or the same device fingerprint; absent that corroboration, disjoint MAIDs block.
Three further guards catch the remaining over-merge vectors. Simultaneous activity from different networks is a hard block — no confidence arithmetic applies. A device fingerprint in simultaneous use across separate records is a collision — two machines coincidentally hashing alike — and the fingerprint itself is quarantined from future matching. And a click ID appearing across more than two other nodes is a forwarded link, not a person: minted per click, a gclid should touch at most a couple of records for one person's devices, so high fan-out values are skipped as bridges entirely.
Finally, consent gates merging itself: if either node carries an identifier marked opted-out or suppressed, the merge is refused. A person who exercised their rights cannot be re-linked into a profile by the machinery working as designed.
A multi-tenant identity graph has an obvious dark temptation: link every tenant's visitors into one super-graph. ClickStream forecloses it cryptographically rather than by policy. Before insertion into shared graph structures, PII-derived signals are keyed with a tenant-specific HMAC: two tenants who both know alice@example.com hold mathematically unrelated HMAC values for her, so their nodes cannot collide — not "are not allowed to," but cannot, because the join key does not exist. Cross-tenant resolution happens only where explicitly enabled, and white-label tenants must affirmatively opt in before contributing to or querying any shared resolution at all.
OAuth subjects get the same treatment with an extra precaution: each provider's subjects are HMAC'd with domain separation, so a Google subject and a Facebook subject can never collide even within one tenant. The same isolation philosophy runs through the platform's key handling — see why a stolen analytics key is useless — and the PII-boundary design in Data Sovereignty and PII Architecture. One consequence worth stating plainly: raw email and phone, where a tenant has collected them with consent, live in the identity graph as the platform's single authoritative PII store — analytics rows carry only hashes and HMACs.
Every ingestion is classified on a five-state scale — human_confirmed, human_likely, unknown, bot_likely, bot_confirmed — feeding a person-eligibility verdict: eligible, review-required, bot-quarantined, or suppressed non-person (test and synthetic traffic). Quarantined nodes fail merge eligibility at the first gate of the pipeline in section 5: a scraper's browsing history can accumulate, but it can never be consolidated into — or contaminate — a person record, never reaches activation, and never syncs to a CRM.
The classification comes from the detection stack documented in The Bot Evasion Atlas — the same edge scoring pass as ClickStream's 26 behavioral models, held to a CI-enforced benchmark of p95 under 3 ms per event. Two design choices complete the loop: vendor-derived identifiers from enrichment partners cap at human_likely, so no purchased record can ever mint a confirmed human; and because classifiers err, the mark-it-human override lets an operator reverse a verdict from the dashboard. Quarantine also has a billing consequence: ClickStream plans bill on human pageviews, so quarantined traffic never consumes your quota.
Article 17 erasure is implemented as a graph operation, not a support ticket. Given the deterministic identifiers a tenant holds for a data subject — hashed email, hashed phone, tenant HMAC variants, visitor and ClickStream IDs — the erasure service removes every row that tenant contributed about the person: signal-store rows, graph edges, raw signal audit logs, and enrichment rows (soft-deleted with their encrypted payloads nulled). The scoping is the point: another tenant's independently collected data about the same person is untouched, because tenant A's erasure obligation is not a license to delete tenant B's records. A node is fully deleted only when no tenant has any remaining data on it.
Between erasure requests, scheduled maintenance runs every six hours: expired probabilistic edges and stale household clusters are pruned, absorbed nodes older than 90 days are garbage-collected, raw PII in audit records older than 90 days is scrubbed, and ambiguous graph states are queued for reprocessing. Data minimization as cron jobs, not a policy PDF — a theme we expand in GDPR as Cron Jobs.
The graph's output surfaces in application code through the Signals API (@clickstreamhq/signals, currently a 0.1.0-alpha developer preview — APIs may change; the stable collection SDK is @clickstreamhq/sdk 1.4.0). The visitor context carries an identity block that reflects resolution state directly:
The states map onto this paper: anonymous is an observed-band node, signal_identified means a deterministic key has been attached, and merged means the canonical pipeline of section 5 has consolidated multiple observation histories — with the absorbed profile IDs listed so your systems can follow the same canonical chain the graph does. A walkthrough of the surrounding API lives in Getting Started with the Signals API.
You may have noticed what this paper omits: no match rate, no coverage percentage, no profile count. That is deliberate. A match rate without a published denominator, an error-rate methodology, and a definition of "match" is not a measurement — and as section 7.3 showed, it is a perverse incentive: every over-merge the architecture exists to prevent would raise it. What we publish instead is falsifiable: the signal families, the gates (0.60, the 30-day window, the five-record mailbox cap, the two-node click-ID cap), the weights, the TTLs, the refusal rules, and the forensic corrections made when a rule was wrong.
Our claim reduces to this: every merge is evidence-scored, every refusal recorded, every consolidation auditable, every person banded by confidence, and non-humans never become people. Judge an identity graph architecture by what it refuses — that is the standard we invite you to hold us to.
Deterministic keys, gated probabilistic matching, confidence bands on every profile — and a graph that records what it refused to merge, not just what it matched.
Start free