Test Scenarios
Given/When/Then format test scenarios for validating a UIAF implementation. Each scenario is testable with browser developer tools (Application tab for storage, Network tab for endpoint payloads, Console for JavaScript state).
All scenarios assume a correctly configured UIAF implementation per the spec (sections 02-08). Cookie name is uiaf_uid, localStorage keys are uiaf_uid and uiaf_attribution, sessionStorage keys are uiaf_uid, uiaf_session_id, and uiaf_attribution.
Identity Lifecycle
Section titled “Identity Lifecycle”1. New visitor on Chrome
Section titled “1. New visitor on Chrome”Given a user with no existing UIAF data in any storage mechanism opens Chrome.
When they navigate to the site for the first time.
Then a new UID is generated in the format {uuid_v4}.{unix_timestamp}. The UID is written to the server cookie (uiaf_uid, Max-Age=34560000, Secure, SameSite=Lax, HttpOnly=false), client cookie, localStorage, and sessionStorage. A new session ID (UUID v4) is stored in sessionStorage under uiaf_session_id. The page_view payload contains identity.is_new: true, identity.resolution_method: "new", identity.confidence: "low", and _meta.identity_method: "new".
2. Return visitor on Chrome
Section titled “2. Return visitor on Chrome”Given a user who visited yesterday and has a valid uiaf_uid cookie.
When they navigate to the site again.
Then the existing UID is read from the server cookie. The cookie Max-Age is refreshed to 34560000 (400 days). The UID is synced to all storage mechanisms. The page_view payload contains identity.is_new: false, identity.resolution_method: "cookie", identity.confidence: "high".
3. Return visitor, cookie cleared but localStorage intact
Section titled “3. Return visitor, cookie cleared but localStorage intact”Given a user whose uiaf_uid cookie has been deleted (by user action or browser extension) but whose localStorage still contains the UID.
When they navigate to the site.
Then the UID is recovered from localStorage. The cookie is re-set with the recovered UID (both server-set and client-set). All storage mechanisms are synced. The page_view payload contains identity.resolution_method: "localstorage_recovery", identity.confidence: "medium". The UID value is identical to the original.
4. Return visitor, all storage cleared
Section titled “4. Return visitor, all storage cleared”Given a user who previously visited but has cleared all site data (cookies, localStorage, sessionStorage).
When they navigate to the site.
Then no existing UID is found in any storage mechanism. A new UID is generated. The user is treated as a brand-new visitor. The page_view payload contains identity.is_new: true, identity.resolution_method: "new". The previous UID is permanently lost from the client side.
5. New visitor on Safari with server-set cookie
Section titled “5. New visitor on Safari with server-set cookie”Given a user on Safari visiting a site with native UIAF integration (server sets Set-Cookie header, server IP matches site IP).
When they navigate to the site for the first time.
Then the server-set cookie has a 400-day lifetime (same as Chrome). The UID is written to localStorage (subject to 7-day interaction purge) and sessionStorage. The page_view payload contains identity.is_new: true.
6. New visitor on Safari with JavaScript-only cookie
Section titled “6. New visitor on Safari with JavaScript-only cookie”Given a user on Safari visiting a site where UIAF sets cookies via document.cookie only (no server-side cookie setting).
When they navigate to the site for the first time.
Then the cookie is created with a maximum effective lifetime of 7 days (Safari ITP caps all JS cookies). localStorage and sessionStorage are set normally. The endpoint payload should reflect _meta.identity_method: "new".
7. Return after 8 days on Safari with JS cookie expired
Section titled “7. Return after 8 days on Safari with JS cookie expired”Given a Safari user whose JS-set cookie was created 8 days ago (expired under ITP 7-day cap). localStorage still contains the UID (user interacted within the 7-day Safari storage window at some point).
When they navigate to the site.
Then the cookie is absent. The UID is recovered from localStorage. The page_view payload contains identity.resolution_method: "localstorage_recovery". If localStorage was also purged (7 days without any site interaction), a new UID is generated instead.
8. Multiple tabs open
Section titled “8. Multiple tabs open”Given a user with an active UIAF session opens a second tab to the same site.
When both tabs are loaded.
Then both tabs share the same UID (via shared cookies and localStorage). Each tab has a DIFFERENT session ID (sessionStorage is per-tab). Endpoint payloads from each tab contain the same identity.uid but different identity.session_id values.
9. Subdomain navigation with Domain cookie
Section titled “9. Subdomain navigation with Domain cookie”Given a user on www.example.com with a uiaf_uid cookie set with Domain=.example.com.
When they navigate to shop.example.com.
Then the same uiaf_uid cookie is accessible on the subdomain. The UID is identical across both subdomains. localStorage is NOT shared (separate instances per subdomain), so localStorage on shop.example.com may not contain the UID until synced from the cookie on first visit.
10. Subdomain navigation without Domain cookie
Section titled “10. Subdomain navigation without Domain cookie”Given a user on www.example.com with a uiaf_uid cookie set WITHOUT a Domain attribute (locked to exact hostname).
When they navigate to shop.example.com.
Then the cookie is not sent to shop.example.com. A new UID is generated on the subdomain. The user has two different UIDs — one per subdomain. This is the expected behavior when the Domain attribute is omitted.
11. Private/incognito window
Section titled “11. Private/incognito window”Given a user opens a private/incognito browser window. When they navigate to the site. Then a new UID is generated (no prior storage exists in the private context). All storage is ephemeral — cookies, localStorage, and sessionStorage are cleared when the window closes. On next private window open, a completely new UID is generated. The endpoint payload reflects a new user each time.
12. SPA route change
Section titled “12. SPA route change”Given a user on a single-page application with an active UIAF session.
When the SPA performs a client-side route change (e.g., React Router navigation).
Then no new UID is generated. The session continues. The identity resolution function is idempotent — it finds the existing UID and uses it. A page_view event may fire for the new route. The identity object in the payload remains unchanged.
13. iframe embed from different domain
Section titled “13. iframe embed from different domain”Given a site at domain-a.com embeds an iframe from domain-b.com that includes UIAF code.
When the page loads.
Then the iframe operates in a third-party context. Storage is partitioned (CHIPS in Chrome, blocked in Safari/Brave/Firefox). The iframe cannot access domain-a.com’s UIAF storage. Any UID created in the iframe is isolated to the embedded context and inaccessible when the user visits domain-b.com directly.
14. Very first visit to site ever
Section titled “14. Very first visit to site ever”Given a site that has just deployed UIAF. No user has any UIAF data anywhere.
When the first visitor arrives.
Then a new UID is generated. All storage mechanisms are populated. The page_view payload contains identity.is_new: true. The attribution.count is 0 if no attribution parameters are present, or 1 if the visit carries UTMs or referrer.
15. Browser with all cookies disabled
Section titled “15. Browser with all cookies disabled”Given a user has disabled all cookies in browser settings.
When they navigate to the site.
Then cookie read/write fails silently. If sessionStorage is available, a session-only UID is stored there. If sessionStorage is also unavailable, no identity is created. The system should not throw errors. If any identity was established, endpoint payloads reflect identity.confidence: "low". If no identity was possible, UIAF should skip identity-dependent operations.
Attribution
Section titled “Attribution”1. First visit with UTMs
Section titled “1. First visit with UTMs”Given a new user with no existing attribution data.
When they arrive via https://example.com/page?utm_source=google&utm_medium=cpc&utm_campaign=spring_sale.
Then first_touch is set with source: "google", medium: "cpc", campaign: "spring_sale". last_touch is set to the same values. count is 1. The page_view payload includes attribution.is_new_touch: true, which the endpoint uses to detect new attribution. Attribution data is stored in localStorage under uiaf_attribution.
2. Second visit with different UTMs
Section titled “2. Second visit with different UTMs”Given a returning user whose first_touch is source: "google", medium: "cpc" from a previous visit.
When they arrive via https://example.com/page?utm_source=facebook&utm_medium=paid_social&utm_campaign=retargeting.
Then first_touch remains unchanged (source: "google", medium: "cpc"). last_touch updates to source: "facebook", medium: "paid_social", campaign: "retargeting". count increments to 2. The page_view payload has attribution.is_new_touch: true, signaling new attribution to the endpoint.
3. Direct visit after campaign
Section titled “3. Direct visit after campaign”Given a user whose last_touch is source: "google", medium: "cpc" from a campaign visit two days ago.
When they navigate directly to the site (no UTM parameters, no meaningful referrer).
Then no attribution update occurs. first_touch and last_touch remain unchanged. count does not increment. The page_view payload has attribution.is_new_touch: false, so the endpoint does not detect new attribution.
4. Visit with gclid and UTMs
Section titled “4. Visit with gclid and UTMs”Given a new user.
When they arrive via https://example.com/page?gclid=Cj0KCQjw...&utm_source=google&utm_medium=cpc&utm_campaign=spring_sale.
Then both click_ids.gclid and the UTM fields are captured in the touchpoint. The first_touch and last_touch objects contain the gclid in click_ids and the UTM values in source, medium, campaign. The gclid is NOT stripped from the URL.
5. Visit with fbclid only (no UTMs)
Section titled “5. Visit with fbclid only (no UTMs)”Given a new user.
When they arrive via https://example.com/page?fbclid=IwAR2F4-dbP... with referrer facebook.com.
Then click_ids.fbclid is captured. source is classified as "facebook" from the referrer domain. medium is classified as "social" from the referrer classification hierarchy. UTM fields (campaign, term, content) are null.
6. Visit with UTMs but no referrer
Section titled “6. Visit with UTMs but no referrer”Given a new user clicking a link from a native email client that does not send a Referer header.
When they arrive via https://example.com/page?utm_source=newsletter&utm_medium=email&utm_campaign=april_update.
Then UTM values are used for classification. source: "newsletter", medium: "email", campaign: "april_update". referrer is null in the touchpoint object. The empty referrer does not prevent attribution capture.
7. Return visit with expired gclid
Section titled “7. Return visit with expired gclid”Given a user who bookmarked https://example.com/page?gclid=Cj0KCQjw... and the gclid’s 90-day validity window has passed.
When they revisit using the bookmarked URL.
Then the gclid is still captured in the click_ids object. The timestamp records the current visit time. The receiving endpoint can compare timestamp against the 90-day window to determine validity. UIAF does not enforce or check expiration at capture time.
8. Two campaign visits in same session
Section titled “8. Two campaign visits in same session”Given a user who arrived via a Google Ads link (gclid + UTMs) and is still in the same browser session.
When they click a Meta ad link in a different tab that opens the same site with ?fbclid=...&utm_source=facebook&utm_medium=paid_social.
Then first_touch remains the Google Ads visit. last_touch updates to the Meta visit. count increments to 2 (or N+1 from prior count). Each page_view payload has attribution.is_new_touch: true, signaling new attribution to the endpoint.
9. Visit from ChatGPT referrer
Section titled “9. Visit from ChatGPT referrer”Given a new user clicking a link in a ChatGPT response.
When they arrive at the site with referrer chatgpt.com and no UTM parameters.
Then the referrer is classified as source: "chatgpt", medium: "ai" per the AI assistant referrer classification. first_touch and last_touch are set with these values. count is 1.
10. Visit with custom parameter in allowlist
Section titled “10. Visit with custom parameter in allowlist”Given the UIAF configuration includes affiliate_id in config.custom_param_allowlist.
When a user arrives via https://example.com/page?affiliate_id=partner123&utm_source=affiliate.
Then affiliate_id: "partner123" is captured as a custom parameter alongside the UTM data. Parameters NOT in the allowlist (e.g., session_token, search_query) are ignored.
Consent
Section titled “Consent”1. No CMP present (Tier 0)
Section titled “1. No CMP present (Tier 0)”Given a site with no consent management platform installed.
When a user visits the site.
Then UIAF operates at full capability. UID persistence uses all storage mechanisms. All attribution parameters (UTMs + click IDs) are captured. Endpoint payloads include consent.tier: 0, consent.cmp_platform: "none", _meta.data_quality: "full".
2. CMP present, user accepts all (Tier 1)
Section titled “2. CMP present, user accepts all (Tier 1)”Given a site with a CMP (e.g., Cookiebot). The user has accepted all consent categories.
When the consent state is read.
Then UIAF operates at full capability. Functionally identical to Tier 0 but with consent proof attached. Payloads include consent.tier: 1, consent.gcm.analytics_storage: "granted", consent.gcm.ad_storage: "granted", _meta.data_quality: "full", _meta.attribution_completeness: "full".
3. CMP present, analytics yes, ads no (Tier 2)
Section titled “3. CMP present, analytics yes, ads no (Tier 2)”Given a user accepted analytics but rejected advertising/marketing categories.
When the consent state is read and attribution data is assembled.
Then UID persistence is enabled (analytics_storage granted). UTMs are captured. Click IDs (gclid, fbclid, etc.) are EXCLUDED from the endpoint payload (click_ids: {}). The URL is NOT modified — click IDs remain in the URL for Google Consent Mode and platform pixels. Payloads include consent.tier: 2, _meta.data_quality: "stripped", _meta.attribution_completeness: "utm_only".
4. CMP present, user has not interacted yet (Tier 3)
Section titled “4. CMP present, user has not interacted yet (Tier 3)”Given an EU visitor lands on a site with a CMP. The consent dialog is visible but the user has not interacted with it.
When UIAF initializes.
Then No persistent UID is created (no cookies, no localStorage writes). A session-only ID may be stored in sessionStorage. Server-side UTM and referrer capture is permitted (no device storage involved). No click IDs are captured. Payloads include consent.tier: 3, identity.uid: null, identity.resolution_method: "ephemeral", _meta.data_quality: "session_only".
5. CMP present, user rejects all (Tier 4)
Section titled “5. CMP present, user rejects all (Tier 4)”Given a page load where the CMP cookie already records a prior “Reject All” decision.
When UIAF initializes and reads the stored consent state.
Then UIAF is fully dormant. No UID, no session ID, no attribution capture. No endpoint call is made. No data is written to any storage mechanism. The UIAF code path exits immediately after reading the consent state. (Note: the final consent_update was already sent on the prior page when the user first clicked “Reject All.” This scenario covers subsequent page loads, not the initial rejection.)
6. User upgrades from Tier 3 to Tier 1
Section titled “6. User upgrades from Tier 3 to Tier 1”Given an EU visitor at Tier 3 (no consent yet). The server has captured UTMs and referrer server-side via Approach B.
When the user clicks “Accept All” on the consent dialog.
Then A new UID is created and written to all storage mechanisms. Pre-consent UTMs from the current page’s server-injected template context are read and merged as first_touch attribution. A server-set cookie is requested via POST /api/uiaf/cookie. A consent_update event is sent with consent.tier: 1 and identity.is_new: true. Attribution data is captured retroactively where possible. The system transitions to full capability without requiring a page reload.
7. User downgrades from Tier 1 to Tier 2
Section titled “7. User downgrades from Tier 1 to Tier 2”Given a user at Tier 1 (full consent) who changes preferences to revoke advertising consent.
When the consent update fires.
Then Click IDs are removed from UIAF’s localStorage and sessionStorage attribution data. The UID is NOT deleted (analytics storage is still granted). A consent_update event is sent with consent.tier: 2. Future payloads have click_ids: {} and _meta.attribution_completeness: "utm_only".
8. User revokes all consent (Tier 1 to Tier 4)
Section titled “8. User revokes all consent (Tier 1 to Tier 4)”Given a user at Tier 1 who revokes all consent.
When the consent change fires.
Then One final consent_update event is sent with consent.tier: 4 and the current UID (last time it will be sent). All UIAF storage is deleted: server cookie, client cookie, localStorage keys (uiaf_uid, uiaf_attribution, uiaf_retry_queue), sessionStorage keys (uiaf_uid, uiaf_session_id, uiaf_attribution). The retry queue is cleared to prevent queued payloads from leaking after revocation. The system goes fully dormant. No further endpoint calls occur.
9. GPC detected in California
Section titled “9. GPC detected in California”Given a user’s browser sends navigator.globalPrivacyControl === true. The user is geolocated to California.
When UIAF reads consent state.
Then GPC is checked BEFORE CMP state. Regardless of what the CMP says, the minimum tier is T2 (no ad data shared). If the implementation takes a strict interpretation, T4 (fully dormant). The endpoint payload includes consent.gpc_signal: true. Click IDs are excluded from all payloads.
10. GPC detected in non-binding jurisdiction
Section titled “10. GPC detected in non-binding jurisdiction”Given a user’s browser sends navigator.globalPrivacyControl === true. The user is in Germany (EU).
When UIAF reads consent state.
Then GPC is noted (consent.gpc_signal: true) but does not override the CMP state. The EU does not specifically reference GPC in GDPR or ePrivacy. The consent tier is determined by the CMP interaction as normal. GPC is treated as informational, not binding.
11. Consent changes mid-page in SPA
Section titled “11. Consent changes mid-page in SPA”Given a user on a single-page application at Tier 3. They interact with the consent dialog without navigating away. When the CMP fires a consent update callback. Then UIAF transitions tiers immediately. No page reload is needed. The consent state change listener triggers identity creation (if upgrading) or storage deletion (if downgrading). Subsequent endpoint calls reflect the new tier. Events that occurred before the change retain their original consent tier in already-sent payloads.
12. CMP loads late (async)
Section titled “12. CMP loads late (async)”Given the CMP JavaScript loads asynchronously and is not available when UIAF initializes. When UIAF attempts to read consent state and finds no CMP. Then UIAF defaults to Tier 3 (pre-consent) when a CMP is expected but absent. It does NOT assume Tier 0. When the CMP eventually loads and fires its ready callback, UIAF reads the consent state and transitions to the appropriate tier.
13. CMP cookie corrupted
Section titled “13. CMP cookie corrupted”Given the CMP’s consent cookie exists but contains invalid or unparseable data. When UIAF attempts to read consent state. Then UIAF treats this as no consent (Tier 3 or Tier 4 depending on configuration). A corrupted consent cookie is never interpreted as consent granted. The system fails safe — no storage, no tracking until valid consent is established.
14. User in US with no consent requirement
Section titled “14. User in US with no consent requirement”Given a US-only site with no CMP installed and no legal consent obligation.
When a user visits.
Then UIAF operates at Tier 0. Full capability. No consent overlay displayed. All storage mechanisms active. All attribution parameters captured. Endpoint payloads include consent.tier: 0, consent.cmp_platform: "none", consent.gpc_signal: false (or true if the browser sends it, but no action required outside binding jurisdictions).
15. EU user with server-side UTM capture before consent
Section titled “15. EU user with server-side UTM capture before consent”Given an EU visitor arrives from a Google Ads campaign with gclid and UTMs. The server implements Approach B (server-side pre-consent capture). When the first HTTP request is processed, before any consent interaction. Then The server reads UTM parameters from the URL and the Referer header. UTMs and referrer domain are injected into the current page’s template context (NOT stored on the user’s device). Click IDs (gclid) are NOT captured server-side (personal data). No cookies or localStorage are written. When consent is later granted on the same page, the client reads the pre-consent UTMs from the template context and combines them with the newly created UID.
Browser Resilience
Section titled “Browser Resilience”1. Safari with JS-only cookies, return after 3 days
Section titled “1. Safari with JS-only cookies, return after 3 days”Given a Safari user with a JS-set uiaf_uid cookie created 3 days ago.
When they return to the site.
Then the cookie is still valid (within the 7-day ITP cap). The UID is read from the cookie. The cookie expiry is refreshed for another 7 days. Normal operation continues. identity.resolution_method: "cookie" (client-side code cannot distinguish server-set from client-set cookies).
2. Safari with JS-only cookies, return after 8 days
Section titled “2. Safari with JS-only cookies, return after 8 days”Given a Safari user whose JS-set cookie was created 8 days ago. The cookie has expired per ITP’s 7-day cap.
When they return to the site.
Then the cookie is absent. UIAF checks localStorage. If localStorage contains the UID (the user interacted with the site within Safari’s 7-day storage purge window), the UID is recovered and the cookie is re-set. The page_view payload contains identity.resolution_method: "localstorage_recovery". If localStorage was also purged, a new UID is generated.
3. Safari with gclid in URL, JS cookie
Section titled “3. Safari with gclid in URL, JS cookie”Given a Safari user arriving from a Google Ads click. The URL contains ?gclid=.... The referrer (google.com) is classified as a tracker by ITP.
When UIAF sets a cookie via JavaScript.
Then Safari ITP detects link decoration from a classified tracker domain and caps the JS cookie at 24 hours (not the usual 7 days). The UID cookie will expire after 24 hours unless refreshed by another visit. localStorage serves as the recovery mechanism after 24 hours.
4. Safari with server-set cookie
Section titled “4. Safari with server-set cookie”Given a Safari user visiting a site with native UIAF integration. The server sets the cookie via Set-Cookie header. The server’s IP address matches the site’s IP address.
When the cookie is set.
Then the cookie receives the full 400-day lifetime. Safari ITP’s JavaScript cookie restrictions do not apply to server-set cookies from same-IP first-party origins. This is the recommended configuration for Safari resilience.
5. Brave browser, JS cookie
Section titled “5. Brave browser, JS cookie”Given a Brave user visiting a site where UIAF sets cookies via document.cookie.
When the cookie is set.
Then Brave caps the cookie lifetime at 7 days maximum, regardless of the requested Max-Age. This is a hard browser-level limit, not configurable by the user. localStorage is available as backup (Brave does not purge first-party localStorage on a timer).
6. Brave browser, server-set cookie
Section titled “6. Brave browser, server-set cookie”Given a Brave user visiting a site with native UIAF integration (server-set Set-Cookie header).
When the cookie is set.
Then Brave caps server-set first-party cookies at 180 days maximum. The cookie Max-Age of 34560000 (400 days) is silently reduced to approximately 15552000 (180 days). This is the maximum achievable on Brave. The cookie is refreshed on each visit.
7. Firefox strict mode
Section titled “7. Firefox strict mode”Given a user on Firefox with Enhanced Tracking Protection set to Strict. When they visit the site. Then First-party storage is NOT restricted. First-party cookies persist up to 400 days. localStorage is persistent with no time-based limit. Strict mode blocks third-party tracking content and fully blocks cross-site cookies (no partitioning), but first-party UIAF storage is unaffected. Full capability.
8. Chrome with uBlock Origin
Section titled “8. Chrome with uBlock Origin”Given a Chrome user with uBlock Origin installed (using standard EasyList + EasyPrivacy filter lists).
When they visit a site with native UIAF integration.
Then UIAF application code loads normally (it is same-origin, not on any filter list). Endpoint calls to the site’s own domain (e.g., /api/collect) are not blocked. External scripts that would be blocked (gtag.js, fbevents.js) are irrelevant because UIAF does not depend on them. Full capability.
9. User clears site data
Section titled “9. User clears site data”Given a returning user who goes to browser settings and clears all site data for the domain.
When they visit the site again.
Then All UIAF storage is gone (cookies, localStorage, sessionStorage). The user is treated as a brand-new visitor. A new UID is generated. The page_view payload contains identity.is_new: true. Previous attribution history is lost from the client side. This is expected and correct — UIAF cannot persist data that the user has explicitly deleted.
10. Browser update changes tracking rules
Section titled “10. Browser update changes tracking rules”Given a browser ships an update that introduces new storage restrictions (e.g., a hypothetical Chrome JS cookie cap). When users with the updated browser visit the site. Then UIAF degrades gracefully per the new constraints. The multi-mechanism storage hierarchy means the most resilient available mechanism is used. If JS cookies are capped but server-set cookies are not, server-set cookies become the primary. If new storage types are restricted, the remaining mechanisms continue to function. No code change is required unless the browser blocks all first-party storage entirely.
Cross-Device and Cross-Domain
Section titled “Cross-Device and Cross-Domain”1. User logs in on mobile
Section titled “1. User logs in on mobile”Given a user on their phone with UID-A (anonymous).
When they log in with user@example.com.
Then the website hashes the email (SHA-256, after normalization: lowercase, trim, Gmail dot removal). An identity_link event is sent to the endpoint containing identity.uid: "UID-A" and identifiers.email_hash: "{sha256_hash}". The website does not store the identity graph. The endpoint associates the email hash with UID-A.
2. Same user logs in on desktop
Section titled “2. Same user logs in on desktop”Given the same user from scenario 1 visits on their laptop. They receive UID-B (a different anonymous UID).
When they log in with the same email user@example.com.
Then An identity_link event is sent with identity.uid: "UID-B" and the same identifiers.email_hash (same email, same normalization, same hash). The endpoint detects that this email hash is already associated with UID-A. The endpoint updates its identity graph: UID-A and UID-B represent the same person. The website on the laptop has no knowledge of UID-A or the phone session.
3. Cross-domain: user clicks link with token
Section titled “3. Cross-domain: user clicks link with token”Given a user on domain-a.com (UID-A) clicks a link to domain-b.com. Domain A generates a short-lived, single-use _uiaf_token and appends it to the URL.
When the user arrives at domain-b.com/page?_uiaf_token=xyz789.
Then Domain B validates the token server-side against the token service. The token resolves to UID-A. Domain B sets UID-A as the user’s cookie on domain-b.com. The _uiaf_token parameter is stripped from the visible URL via history.replaceState. The user has the same UID on both domains.
4. Cross-domain token expired
Section titled “4. Cross-domain token expired”Given a user clicks a link from domain-a.com to domain-b.com with a _uiaf_token, but the token has expired (older than 120 seconds) or has already been used.
When the user arrives at domain-b.com.
Then Domain B’s server-side validation rejects the token. No cross-domain identity linking occurs. Domain B generates a new UID for the user. The _uiaf_token parameter is still stripped from the URL. The user has different UIDs on each domain.
5. User logs in with different email on same device
Section titled “5. User logs in with different email on same device”Given a user with UID-A on their browser. They previously sent an identity_link with email_hash_1.
When they log in with a different email address, producing email_hash_2.
Then A second identity_link event fires with the same identity.uid: "UID-A" and the new identifiers.email_hash: "email_hash_2". The endpoint receives both links and must decide: if this is the same person with multiple emails, merge into one identity cluster; if this is a shared device with different people, keep them separate. The website sends both events without judgment — conflict resolution is the endpoint’s responsibility.