Identity Resolution
Edition 3.1.1
Why Identity Resolution Matters
Section titled “Why Identity Resolution Matters”Where persistent identity is permitted, a visitor gets a UID: a random identifier that links their visits together. The UID contains no name, email, or other readable content, yet it is still personal data, never anonymous, because it can re-identify the same browser. Where persistent identity is not permitted, identity fields stay empty or temporary (null/ephemeral per Identity Management). Identity resolution links that UID to verified account evidence when, and only when, the site holds it. Verified evidence means a login, an authenticated account assertion, a verified link or one-time code, or a completed payment. Without it, one customer on three devices is three identities and a fragmented journey.
When identify May Fire: Verified Evidence Only
Section titled “When identify May Fire: Verified Evidence Only”Deterministic graph association requires verified or authenticated evidence:
- Login / authenticated session: the user proved control of the account.
- Account creation with verification: verified link or OTP completed.
- Payment: a completed payment instrument check.
- Verified profile update: a re-verified identifier change.
Unverified newsletter, contact, or lead-form input cannot emit identify and never merges identity clusters. An attacker typing a victim’s email into a form must not join the victim’s identity to the attacker’s device. Where a basis exists, unverified form input may be forwarded as conversion data under its own purpose. It is business data, not identity evidence.
What identify Carries
Section titled “What identify Carries”identify.event_data has exactly {identifiers, extensions?}. The identifiers list carries one to eight verified identifiers (evidence objects), each a typed, normalized, hashed digest with its verification state.
| Field | Contract |
|---|---|
type | email | phone | customer |
digest | exact lowercase 64-hex |
algorithm | sha256 | hmac_sha256 |
key_version | conditional — required only for hmac_sha256, prohibited for sha256; ^[a-z][a-z0-9_-]{0,31}$, 1–32 ASCII bytes |
normalization_profile | version-bearing name per the registry below |
verification | closed object: state ∈ authenticated | verified; method ∈ authenticated_session | verified_link | verified_otp | payment; integer Unix-seconds time in 0–253402300799 |
provenance | 1–128 UTF-8 bytes — issuer/application origin of the evidence |
tenant_binding | 1–128 UTF-8 bytes — tenant namespace/binding |
Two validators share the work. JSON Schema enforces object closure, required fields, enums, patterns, integer ranges, and item counts. The shared producer/endpoint/CI reference validator also enforces the UTF-8 and RFC 8785 byte ceilings and the prohibited-content rules schema cannot express. Producers reject before body freeze; endpoints reject before processing. Raw identifiers, zero-entry arrays, more than eight entries, over-bound canonical arrays, missing provenance, empty-input digests, and inconsistent key-version use all fail validation. Repeated delivery of the same evidence is idempotent and never increases confidence.
Producer boundary. The UIAF identify producer accepts only bounded, profile-tagged digest evidence, never raw or canonically cleaned email/phone/customer values. If a deployment hashes email or phone in the browser, that hashing is application-owned preprocessing that runs before the producer call. It must generate every required profile-specific digest before dispatch using the published profiles. Customer/account identifiers are server-only keyed digests (hmac_sha256 with its key_version): the key never reaches the browser and no browser fallback exists. An unsalted hash of a low-entropy account ID is not permitted. Browser/application SHA-256 preprocessing accepts only email and phone identifiers. A customer identifier, or any unknown/non-schema type, reaching it is rejected by an executable guard before any raw-value handling, profile application, or hashing, and produces no evidence.
Minimized Envelope
Section titled “Minimized Envelope”The privacy-minimized identify variant carries only the common metadata needed for consent, ordering, and evidence, plus its event_data. attribution, page, and client are required keys whose whole-group values are null by default. null is valid; a non-null group must validate the complete registered group schema, and populating one is permitted only through a registered, versioned deployment profile, never ad-hoc prose. Endpoint routing must not forward first-party identity digests merely because ad data exists elsewhere in the envelope. The canonical identify example is the shared generated v3 fixture published with the Endpoint Schema artifacts: one fixture, schema-validated in CI, used by both sections rather than maintained twice.
Normalization Profiles (Pre-Dispatch Only)
Section titled “Normalization Profiles (Pre-Dispatch Only)”A digest cannot be re-normalized. Divergent destination requirements are handled where the raw value legitimately exists: the application or trusted website backend applies each required versioned profile and creates separate digests before dispatch. Raw or canonically cleaned identifiers never enter a UIAF event or collection endpoint, and an endpoint never attempts post-hash normalization.
Profile names are version-bearing: exact regex ^[a-z][a-z0-9_]{1,46}_v[0-9]{1,3}$, at most 52 ASCII characters, with no separate version field. The initial registry:
| Profile | Rules |
|---|---|
email_basic_v1 | Lowercase (Unicode Default Case Conversion), trim, NFC — the platform-neutral canonical email profile |
email_google_ads_v1 | email_basic_v1, plus — for gmail.com/googlemail.com local parts only — remove all dots AND strip the plus suffix (Jane.Doe+Shopping@googlemail.com → janedoe@googlemail.com; Google Ads API enhanced-conversions normalization, primary-verified 2026-07-19). Dots and plus suffixes on every other domain are significant and preserved |
phone_e164_v1 | E.164: +, country code, digits only |
customer_keyed_v1 | Server-side keyed digest of the tenant-scoped customer identifier (hmac_sha256 + key_version) |
A Meta-specific profile is deliberately omitted until its rules are primary-source verified; when registered it ships with source and last-reviewed date.
Empty or invalid input produces null, never a digest. The SHA-256 of the empty string (e3b0c442…) appearing anywhere is a validation error and a known bug signature.
// PSEUDOCODE — application-owned preprocessing, BEFORE the producer call
function digestsFor(type, rawValue, requiredProfiles): if type not in {"email", "phone"}: return null // EXECUTABLE GUARD, first check — before // any raw-value handling, profile // application, or hashing: this SHA-256 // path accepts email/phone ONLY. customer // and every unknown/non-schema type are // rejected here and produce NO evidence. // Customer evidence exists only as the // tenant-separated, server-side keyed // hmac_sha256 digest with key_version; // the key never reaches the browser and // no browser fallback exists. if rawValue is null or trim(rawValue) == "": return null // never hash empty input if requiredProfiles is empty: return null // no required profiles -> no evidence // requiredProfiles is ATOMIC per identifier: one null/invalid/empty // normalization rejects this identifier's ENTIRE required-profile set // BEFORE ANY HASH or dispatch — partial required-profile evidence never // exists. Two explicit phases enforce that order executably.
// PHASE 1 — normalize and validate EVERY required profile into an // ephemeral set. NO hashing and NO evidence creation happens here. normalizedSet = [] for profile in requiredProfiles: // e.g. email_basic_v1 + email_google_ads_v1 normalized = applyProfile(profile, rawValue) // exact registry rules if normalized is null or normalized == "" or not isValid(normalized): discard normalizedSet // whole-identifier rejection: the return null // ephemeral set is discarded with ZERO // sha256 invocations — no digest of an // earlier valid profile ever existed normalizedSet.push({ profile: profile, normalized: normalized })
// PHASE 2 — only after the COMPLETE required set passed phase 1: // hash and build evidence for all entries. entries = [] for item in normalizedSet: entries.push({ type: type, digest: sha256_lowercase_hex(item.normalized), algorithm: "sha256", normalization_profile: item.profile, verification: currentVerification(), provenance: appProvenance(), tenant_binding: tenantBinding() }) return entries // (Negative fixtures, instrumented: with an instrumented applyProfile/sha256 // call sequence, digestsFor("email", raw, [email_basic_v1, email_google_ads_v1]) // where email_basic_v1 succeeds and the LATER email_google_ads_v1 returns // null records applyProfile reaching BOTH profiles in order while the // sha256 invocation count remains exactly ZERO — no evidence is built and // nothing is dispatched. digestsFor("customer", // rawAccountId, [customer_keyed_v1]) => null, and unknown-type // digestsFor("loyalty_number", rawValue, [email_basic_v1]) => null — in // both, applyProfile and sha256 are never invoked and no evidence object // is produced; only the server-side keyed HMAC path yields customer // evidence.)Hashing Is Pseudonymization, Not Protection
Section titled “Hashing Is Pseudonymization, Not Protection”Deterministic digests of enumerable inputs are pseudonymization, not anonymization and not a security control. The email/phone input space is dictionary-attackable, and every identifier digest and customer ID remains personal data, requiring TLS, access control, retention limits, purpose limitation, and log redaction. The keyed-digest profile’s key is server-side with ownership, rotation (key_version), and tenant separation specified in the Endpoint Schema and Data Handling.
Never hash click IDs or platform cookie values: platforms match on raw values; the privacy control there is consent, not hashing.
Cross-Device Linking
Section titled “Cross-Device Linking”Cross-device resolution is reliable only through authenticated sessions: deterministic evidence on each device. Probabilistic matching is unreliable, legally risky, and unexpected by users; UIAF does not use it. Fingerprinting is regulated by ePrivacy Article 5(3) (it accesses information from terminal equipment, per EDPB Guidelines 2/2023), with data minimization (Art. 5(1)(c) GDPR) as a supporting principle; UIAF does not use it. On current Safari, Advanced Fingerprinting Protection targets known fingerprinting scripts (not every script; no universal fingerprint resistance is implied). Link Tracking Protection is documented for Mail, Messages, and Private Browsing.
Endpoint Invariants (graph out of scope; its contract is not)
Section titled “Endpoint Invariants (graph out of scope; its contract is not)”- Tenant isolation: identical digests in different tenant bindings never associate.
- Idempotent associations: retries and repeated identical evidence never inflate confidence.
- Shared devices: a device UID is not a person identifier. One UID with many distinct verified identities is a many-to-many association (or a flagged shared device), never one merged person.
- Time-bounded associations where appropriate, and defined update/unlink/account-switch/logout semantics, so one household member’s future activity is not attributed to another.
Cross-Domain Handoff
Section titled “Cross-Domain Handoff”Subdomains share identity via the cookie Domain attribute (Identity Management). Separate registrable domains use the token handoff below; the normative mint/redeem contracts live in the Endpoint Schema auxiliary contracts.
Mint. The browser performs a same-origin top-level form navigation POST /api/uiaf/handoff with Content-Type: application/x-www-form-urlencoded.
Token. Opaque, ≥128 CSPRNG bits, base64url, 22–43 characters. Atomically single-use. Bound to tenant, issuer/source origin, exact allowlisted target audience, identity_handoff purpose, and the exact clean destination. handoff_token_ttl_seconds defaults to 120 and is configurable only 30–300.
Redeem. While handling the target request and before emitting HTML or subresources, the target server makes an authenticated server-to-server POST /api/uiaf/handoff/redeem with application/json and exactly {token, target} (target = the exact clean absolute URL being handled, without _uiaf_token).
Consent and conflicts. Effective permissions and the GPC overlay are checked at issuance and consumption; when persistence is not allowed, no persistent UID is minted or adopted. A valid existing target cookie is never overwritten. When policy permits, the server records an idempotent alias/link instead, or declines the handoff. The returned UID and tenant namespace are validated before use; outstanding tokens are revoked on consent withdrawal where feasible.
Chrome Bounce Tracking Mitigations. This handoff is redirect-shaped, and Chrome’s Bounce Tracking Mitigations (default since October 2023) heuristically purge storage for bounce-redirect domains in the third-party-cookie-blocked population, which includes Incognito by default. The documented out-of-scope classes are federated authentication, SSO, and payments; an attribution handoff is not among them. Each site Chrome may classify needs its own recorded interaction to avoid purge. Treat Chrome BTM as a residual risk for this flow. See Browser Landscape for the dated details.
Iframe-based cross-domain identity is not a dependable cross-browser design. Safari, Firefox, and Brave block or partition third-party storage, private modes and user settings narrow it further, and Chrome’s storage partitioning isolates non-cookie state. Default Chrome still permits unpartitioned third-party cookies under Google’s current policy (privacysandbox.com/news/privacy-sandbox-next-steps, accessed 2026-08-07; ledger B15), so the claim is portability, not universal blocking. Chrome’s Related Website Sets, its related-domain storage-sharing mechanism, was retired with the Privacy Sandbox wind-down on 2025-10-17. (See Identity Management for the CHIPS state and Brave’s always-rejecting Storage Access behavior, and Browser Landscape for the retirement ledger.)
Edge Cases
Section titled “Edge Cases”Different verified email, same device. Two evidence tuples on one UID: the endpoint keeps both associations under its invariants. Merge questions are graph policy, not client concern.
Shared family computer. Many verified identities on one UID: many-to-many handling or shared-device flagging, never a five-email “person.”
Cookies cleared, then login. New UID-B plus the same verified tuple: the endpoint links UID-B into the existing cluster; history under UID-A is preserved server-side.
Consent constraints. Two independent gates apply (Consent Integration). First, first-party linking requires its documented first-party basis (analytics consent, or contract for authenticated account features) and is compatible with an endpoint no-forward policy. Second, each downstream disclosure is separately gated per destination: Google forwarding by ad_user_data, other platforms by their own marketing consent. ad_user_data is a Google-destination signal, not a global identity switch. Tier labels are derived display shorthand only.
Normative Requirements
Section titled “Normative Requirements”BCP 14 requirement keywords are normative only inside the identified blocks below; all other prose in this section is explanatory.
- UIAF-06-EVID-001 — The
identifyproducer MUST accept only bounded, profile-tagged digest evidence and MUST reject raw or canonically cleaned identifier values. Null, invalid, or empty normalized output MUST NOT be hashed. If any required normalization profile for one identifier returns null, invalid, or empty output, that identifier’s complete required-profile set MUST be rejected before hashing or dispatch and MUST NOT emit partial evidence; other independently verified identifiers remain only when each has its own complete required set. When no valid evidence remains, noidentifyMUST be emitted. Normalization and validation of the complete required-profile set MUST complete before the first hash is computed — a two-phase order in which no digest of an earlier valid profile exists when a later required profile fails. (Negative fixture:[email_basic_v1, email_google_ads_v1]with the later profile returning null — an instrumented hash sequence records zero SHA-256 invocations, nothing hashed, no one-profile partial evidence.) - UIAF-06-EVID-002 — Unverified newsletter, contact, or lead-form input MUST NOT emit
identifyand MUST NOT merge identity clusters. Repeated delivery of identical evidence MUST be treated as idempotent, never confidence-increasing. - UIAF-06-EVID-003 — Browser/application SHA-256 preprocessing MUST accept only
emailandphoneidentifiers. Acustomer/account identifier — or any unknown/non-schematype— passed to it MUST be rejected by an executable guard before any raw-value handling, profile application, or hashing, and MUST produce no evidence. Customer evidence MUST be produced only by the tenant-separated, server-side keyedhmac_sha256digest with itskey_version; the key MUST NOT reach the browser and no browser fallback exists. (Negative fixtures: a raw customer/account ID, and any unknown-type value, passed todigestsFornever reachapplyProfileor SHA-256 and produce no evidence.) - UIAF-06-HANDOFF-001 — The handoff target MUST consume the token server-side before emitting HTML or subresources and MUST respond with a clean allowlisted
303on every redeem outcome. The raw token MUST NOT appear in any access or application log, and a redeem failure MUST route to normal consent-aware identity resolution — a UID MUST NOT be minted or adopted unconditionally.