Consent Integration
⚖️ The Fundamental Trade-Off
Section titled “⚖️ The Fundamental Trade-Off”Sections 02-06 describe the technical ceiling: full UID persistence, all click IDs, attribution across sessions. Consent determines how far below that ceiling you operate.
The core tension: full compliance with EU privacy law means losing first-touch attribution for the majority of your traffic:
User clicks a Google Ad (gclid in URL) -> Lands on site -> No consent yet -> No UID created (ePrivacy Art 5(3) prohibits device storage without consent) -> No attribution captured to persistent storage -> Consent dialog appears -> 50-70% of users: reject or ignore -> session ends with ZERO persistent data -> 30-50% of users: accept -> NOW create UID, but referrer is already gone from document.referrer, gclid may still be in URL if no navigation occurredThis section defines what “degraded” means at each level and where the gray areas are.
📡 Google Consent Mode as the Signal Layer
Section titled “📡 Google Consent Mode as the Signal Layer”Google Consent Mode (GCM) provides a standardized signal that UIAF reads but does not control. Your CMP sets these signals. UIAF reads them to determine its operating tier.
The Four Signals
Section titled “The Four Signals”| Signal | Controls | Default |
|---|---|---|
analytics_storage | GA4 cookies, analytics-purpose storage | denied |
ad_storage | Advertising cookies, ad-purpose storage | denied |
ad_user_data | Sending user-provided data (hashed PII) to Google for ad matching | denied |
ad_personalization | Using data for remarketing audiences | denied |
Set via gtag('consent', 'default', {...}) on page load, updated via gtag('consent', 'update', {...}) on consent interaction. Readable from the dataLayer array.
Advanced vs. Basic Mode
Section titled “Advanced vs. Basic Mode”Advanced mode sends cookieless pings even when denied. The pings contain timestamp, page URL, referrer, consent state, user agent — but NO cookies, NO client_id, NO user identifiers. Google uses them to model conversion rates for unconsented traffic.
Basic mode sends nothing when denied.
Recommendation: Advanced mode. The pings are non-identifying and give you conversion estimates for the 50-70% of traffic you otherwise lose. This is the single highest-leverage action for recovering value from denied consent. Caveat: modeling requires minimum thresholds (~1,000 events/day for GA4, ~700 clicks/day for Ads). Below that, no modeling occurs.
📖 Reading Consent State
Section titled “📖 Reading Consent State”UIAF must read consent state from whatever mechanism the site uses. Three options:
- Google Consent Mode state — read from
dataLayer. Most universal; nearly every CMP integrates with GCM. - CMP cookie directly — Cookiebot:
CookieConsent(JSON withstatistics,marketingbooleans). OneTrust:OptanonConsent(URL-encodedgroups=parameter). - Custom signal — any site-specific consent mechanism.
ConsentState Interface
Section titled “ConsentState Interface”ConsentState { analytics_allowed: boolean // Can we store identity and send analytics data? ads_allowed: boolean // Can we capture and send advertising click IDs? ad_user_data_allowed: boolean // Can we send user-provided data (hashed PII) for ad matching? personalization_allowed: boolean // Can data be used for remarketing/personalization? raw_string: string | null // Pass-through of original CMP consent string (for audit) platform: "cookiebot" | "onetrust" | "custom" | "none" gpc_detected: boolean // Global Privacy Control signal present in browser}Reading Logic
Section titled “Reading Logic”Read in order: (1) Check navigator.globalPrivacyControl — GPC overrides all other signals in binding jurisdictions. (2) Read CMP cookie/API directly — this is the source of truth for the user’s actual consent choice. (3) Read GCM state from dataLayer as a fallback if CMP is unavailable — GCM can lag behind CMP state or be mis-mapped. (4) Default to T3 (not T0) if a consent mechanism is expected but has not loaded yet. Default to T0 only if no consent mechanism is present on the site at all.
Why CMP before GCM: The CMP cookie reflects the user’s direct choice. GCM signals are set by the CMP’s integration code and can be stale, incorrectly mapped, or delayed by async loading. When CMP and GCM disagree, trust the CMP.
🎚️ Consent Tiers
Section titled “🎚️ Consent Tiers”Five tiers from maximum capability to fully dormant.
Tier 0 — Maximum Capability (No Consent Mechanism)
Section titled “Tier 0 — Maximum Capability (No Consent Mechanism)”No CMP is present on the site. No consent signals exist to read.
| Capability | Status |
|---|---|
| UID persistence | Full (server cookie + client cookie + localStorage + sessionStorage) |
| Attribution capture | Full (UTMs + all click IDs + referrer) |
| Endpoint payload | Complete, no fields suppressed |
| Consent object in payload | consent.tier: 0 or absent |
Use cases: internal tools, US-only sites without legal consent obligation, development and staging environments, B2B sites operating under legitimate interest (non-EU).
The system behaves exactly as described in sections 02-06. No degradation.
Tier 1 — Full Consent Granted
Section titled “Tier 1 — Full Consent Granted”User has accepted all consent categories (analytics + advertising). Functionally identical to T0, but with consent proof attached.
| Capability | Status |
|---|---|
| UID persistence | Full |
| Attribution capture | Full |
| Endpoint payload | Complete |
| Consent proof | Included in every payload |
Every payload includes consent.tier: 1, _meta.data_quality: "full", _meta.attribution_completeness: "full", plus the full ConsentState object with GCM signals and CMP consent string for audit.
Use case: EU site where the user accepted all categories.
Tier 2 — Partial Consent (Analytics Yes, Ads No)
Section titled “Tier 2 — Partial Consent (Analytics Yes, Ads No)”User accepted analytics but rejected advertising/marketing categories. This is the most common partial consent state.
| Capability | Status | Reason |
|---|---|---|
| UID persistence | Yes | analytics_storage granted = identity cookies permitted |
| UTM capture | Yes | UTMs are not personal data, and analytics storage is granted |
| Click ID capture | Excluded from UIAF payload | Click IDs are personal data used for ad matching; ad_storage denied |
| Referrer capture | Yes | Not personal data |
| Endpoint payload | Sent, with click IDs stripped |
Critical distinction: UIAF does NOT strip click IDs from the URL. Removing gclid or fbclid from the URL breaks Google’s own Consent Mode detection, platform pixels, and downstream deduplication (see 03-attribution-capture.md). UIAF simply excludes click IDs from its own payload.
{ "attribution": { "first_touch": { "source": "google", "medium": "cpc", "campaign": "spring_sale", "click_ids": {}, "referrer": "google.com", "landing_page": "/products/shoes", "timestamp": 1647291600 } }, "consent": { "tier": 2 }, "_meta": { "data_quality": "stripped", "attribution_completeness": "utm_only" }}Platform impact at T2:
- GA4: works normally. GA4 operates on
analytics_storage, which is granted. - Google Ads Enhanced Conversions: loses
gclidmatching from UIAF. Falls back to hashed email matching if available from the site’s own form submissions. - Meta CAPI: loses
fbclidmatching. Falls back to hashed email/phone if available. - All ad platforms: lose click-based attribution from UIAF. Google Consent Mode Advanced (if enabled) still provides modeling data via cookieless pings.
Tier 3 — Pre-Consent / Functional Only
Section titled “Tier 3 — Pre-Consent / Functional Only”No consent has been granted, or only strictly necessary/functional consent is active. This is the state every EU visitor is in before interacting with the consent dialog.
| Capability | Status | Reason |
|---|---|---|
| Persistent UID | No | No cookies, no localStorage for identity (ePrivacy Art 5(3)) |
| Session ID | sessionStorage (see legal risk note below) | ePrivacy Art 5(3) applies to sessionStorage, but session-only functional storage has stronger “strictly necessary” arguments |
| Click ID capture | No | Personal data, no consent |
| UTM capture | Server-side only (see below) | |
| Endpoint payload | Minimal, session-scoped |
The gray area: server-side capture.
ePrivacy Art 5(3) regulates “storing or accessing information on the terminal equipment.” Reading HTTP headers server-side does neither — it is the server processing its own request.
- Referer header: legal. Standard HTTP processing.
- UTM parameters from URL: legal. Not personal data, not device storage.
- Click IDs from URL: NOT captured. Personal data under GDPR Recital 30; storing/processing requires consent.
- UTMs injected into page template: permitted. Server reads HTTP headers and injects into the current page’s template context — no device storage involved.
- Writing to user’s browser: NOT permitted without consent.
This gives T3 stripped attribution without device storage. The server captures UTMs and referrer on the HTTP request and injects them into the current page’s template context. If consent is later granted on the same page, the client reads them from the template context and combines them with the new UID.
{ "event": "page_view", "identity": { "uid": null, "session_id": "a1b2c3d4-...", "is_new": false, "resolution_method": "ephemeral", "confidence": "low" }, "consent": { "tier": 3, "..." : "..." }, "attribution": { "first_touch": null, "last_touch": { "source": "google", "medium": "cpc", "campaign": "spring_sale", "term": null, "content": null, "click_ids": {}, "referrer": "google.com", "landing_page": "/products/shoes", "timestamp": 1647291600 }, "count": 1, "is_new_touch": true }, "_meta": { "data_quality": "session_only", "attribution_completeness": "server_side_utm_only" }}Analytics value preserved at T3: pageviews per session, session duration, bounce rate, single-session funnels, source/medium/campaign (from server-side capture).
Analytics value lost at T3: unique visitors (every session looks like a new user), return rate, cross-session attribution, cross-session funnels, any metric that requires identity persistence.
sessionStorage for session ID — legal risk. ePrivacy Art 5(3) covers ALL device storage including sessionStorage (EDPB Guidelines 2/2023). The “strictly necessary” exemption is narrow: it covers storage essential for a service explicitly requested by the user (e.g., shopping cart, login state). It does NOT cover analytics session stitching.
A session ID used to connect pageviews within a visit is analytics functionality, not a user-requested service. No DPA has explicitly sanctioned this pattern, but no DPA has enforced against it either.
Recommendation: Treat T3 sessionStorage session IDs as a calculated risk, not a legal safe harbor. Document the decision in your DPIA. In strict jurisdictions (Germany, Austria), consider defaulting to T4 instead of T3. The spec includes session ID at T3 as an option, not a guarantee of compliance.
Tier 4 — Fully Dormant
Section titled “Tier 4 — Fully Dormant”The system is completely inactive. Nothing is stored, nothing is sent, nothing is read from the URL for UIAF purposes.
| Capability | Status |
|---|---|
| UID | None |
| Session ID | None |
| Attribution | None |
| Endpoint call | None — no HTTP request to the UIAF endpoint |
| Storage writes | None |
Use cases:
- User explicitly clicks “Reject All” on the consent dialog
- User with GPC signal in a jurisdiction where it is legally binding (California, Colorado, Connecticut, Texas, Oregon)
- Site policy that honors DNT (rare — Firefox removed DNT in February 2025, but some policies still reference it)
At T4, the UIAF code path exits immediately after reading consent state. No processing occurs.
🧩 The First-Touch Problem
Section titled “🧩 The First-Touch Problem”The biggest practical challenge with consent-aware attribution. The scenario is universal: user arrives from a marketing channel, the consent dialog appears, and by the time consent is granted (if it ever is), the original attribution signal may be degraded or lost.
Three approaches, with increasing aggressiveness:
Approach A: Accept the Loss
Section titled “Approach A: Accept the Loss”Do not capture anything until consent. After consent, create UID and start fresh. The system registers a consent change listener and initializes only when analytics_allowed becomes true.
What you lose: original document.referrer (gone after consent dialog interaction), possibly the gclid (gone if SPA navigated away from landing page). What you keep: simplicity, zero legal risk.
Approach B: Server-Side Pre-Consent Capture (Recommended)
Section titled “Approach B: Server-Side Pre-Consent Capture (Recommended)”On each HTTP request, the server reads the Referer header and URL query parameters. It captures UTMs (not personal data) and the referrer domain. It does NOT capture click IDs (personal data). The captured data is injected into the rendered page template as a server-side variable.
// PSEUDOCODE -- Server-side, runs on each HTTP request
function handleRequest(request, response): utms = { source: request.query.get("utm_source"), medium: request.query.get("utm_medium"), campaign: request.query.get("utm_campaign"), term: request.query.get("utm_term"), content: request.query.get("utm_content") } // Do NOT capture click IDs -- they are personal data pre_consent_attribution = { utms: utms, referrer: extractDomain(request.headers.get("Referer")), landing_page: request.path, timestamp: now() } // Inject into template -- available to client JS on THIS page only response.templateContext.uiaf_pre_consent = pre_consent_attribution renderPage(request, response)What you keep: UTMs and referrer from the current page’s HTTP request. What you lose: click IDs. Legal basis: no device storage triggered, UTMs are not personal data.
Scope limitation: Pre-consent UTMs are available only on the page where they were captured. The server cannot correlate data across page loads without a session identifier — and any such identifier stored on the user’s device would itself require consent, reintroducing the problem. If the user navigates to page 2 before consenting, page 1’s UTMs are lost unless the URL still carries them. This is a fundamental trade-off of consent-first architectures, not a solvable engineering problem.
Approach C: Retroactive URL Capture
Section titled “Approach C: Retroactive URL Capture”After consent, check if click IDs and UTMs are still in window.location.href. If the user has not navigated away from the landing page, the original parameters are still there. On consent grant, parse the current URL, capture gclid/fbclid if ads_allowed, UTMs regardless.
What you keep: potentially everything, if the user is still on the landing page. What you lose: everything, if SPA routing moved them to a new URL. Reliability: depends on user behavior and site architecture. Not dependable as primary strategy.
Comparison
Section titled “Comparison”Recommendation: Approach B for maximum recoverable attribution with full legal defensibility. Combine with Approach C as a supplementary check — after consent, also check the current URL for click IDs that may still be present. The two approaches are complementary, not exclusive.
🔄 Consent Change Handling
Section titled “🔄 Consent Change Handling”Users can modify consent preferences at any time. UIAF must handle transitions between tiers.
State Machine
Section titled “State Machine”Required Actions on Transition
Section titled “Required Actions on Transition”Upgrade (lower tier to higher tier):
- T3 -> T1 or T3 -> T2: Create UID, set all permitted storage, read pre-consent UTMs from template context (Approach B), capture current attribution, send initial payload.
- T2 -> T1: Re-capture click IDs from current URL if still present (Approach C), update
consent.tierand_meta.attribution_completeness, sendconsent_updateevent. - T4 -> T1 or T4 -> T2: Full initialization as if the system just started at the granted tier.
Downgrade (higher tier to lower tier):
- T1 -> T2: Immediately remove click IDs from UIAF storage (localStorage, sessionStorage). Clear retry queue (
uiaf_retry_queue) — queued payloads may contain click IDs from the higher tier. Do NOT delete the UID. Sendconsent_updateevent with new tier. Future payloads exclude click IDs. - T1 -> T3 or T2 -> T3: Delete UID from all storage (cookie, localStorage, sessionStorage). Delete all attribution data from client storage. Clear retry queue (
uiaf_retry_queue). Send finalconsent_updateevent. System enters pre-consent mode. - Any -> T4: Delete ALL UIAF storage. Send one final
consent_updateevent. System goes dormant. No further endpoint calls.
Full revocation (any tier -> T4):
Send one final consent_update event with consent.tier: 4 and the current UID (last time it will be sent). Then delete all UIAF storage: server cookie, client cookie, localStorage (uiaf_uid, uiaf_attribution, uiaf_retry_queue), sessionStorage (uiaf_uid, uiaf_session_id, uiaf_attribution). System goes dormant.
🛡️ GPC (Global Privacy Control)
Section titled “🛡️ GPC (Global Privacy Control)”Browser-level signal (navigator.globalPrivacyControl === true) indicating the user opts out of the sale or sharing of personal data. Successor to DNT, but with actual legal backing.
Check GPC before CMP state. GPC overrides a consent dialog: a user might click “accept all,” but if their browser sends GPC, the legal obligation in applicable jurisdictions is to honor it as an opt-out.
Legal Status by Jurisdiction
Section titled “Legal Status by Jurisdiction”| Jurisdiction | GPC Status | Required UIAF Action |
|---|---|---|
| California (CCPA/CPRA) | Legally binding opt-out of sale/sharing | Minimum T2 (no ad data). T4 if interpretation is strict. |
| Colorado (CPA) | Legally binding universal opt-out | Minimum T2 |
| Connecticut (CTDPA) | Legally binding universal opt-out | Minimum T2 |
| Texas (TDPSA) | Legally binding universal opt-out | Minimum T2 |
| Oregon (OCPA) | Legally binding universal opt-out | Minimum T2 |
| EU/EEA | Not specifically referenced in GDPR/ePrivacy | Treat as signal of user preference, not binding override |
| Other US states | Varies; check applicable law | Default to T2 if unsure |
GPC means “do not sell or share my personal data” — not “block all analytics.” First-party analytics is generally not “sale or sharing.” Conservative: GPC in binding jurisdiction -> T2. Strict: GPC -> T4. Choose based on legal counsel.
📈 Measuring Consent Impact
Section titled “📈 Measuring Consent Impact”The _meta object in every payload enables measurement of consent’s impact on data quality. Without it, you cannot distinguish “fewer conversions” from “fewer measured conversions.”
Key Metrics
Section titled “Key Metrics”- Sessions by tier — what percentage operates at each tier. The fundamental metric.
- Attribution coverage rate — percentage of sessions with at least
sourceandmediumcaptured. At T3, depends on whether Approach B is implemented. - Identity persistence rate — percentage of sessions with a returning UID. At T3/T4 this is always 0%; reveals how much unique-visitor count is inflated.
- Click ID match rate — percentage of paid sessions with click ID in payload. At T2+, drops to zero from UIAF (GCM Advanced provides modeled signal separately).
Segmentation
Section titled “Segmentation”Segment analytics by _meta.data_quality:
"full"— reliable, comparable to pre-consent-era data"stripped"— attribution present but no click IDs"session_only"— no cross-session identity
Alerting
Section titled “Alerting”Alert if T3 + T4 exceeds 70% of sessions. At this level, unique-visitor counts are meaningless, Google modeling becomes unreliable (insufficient consented baseline), and attribution covers less than 30% of conversions. This signals a consent UX or traffic mix problem — not a UIAF problem.
🌳 Tier Decision Tree
Section titled “🌳 Tier Decision Tree”🔮 Future Considerations
Section titled “🔮 Future Considerations”Digital Omnibus Directive. The European Commission proposed (November 2025) an amendment that would exempt first-party analytics from the consent requirement under ePrivacy, provided the analytics serve the site owner’s own purposes and no data is shared with third parties. If adopted, this would make T3 significantly more capable — potentially allowing persistent identity for first-party analytics without consent. Monitor legislative progress. Do not implement ahead of adoption.
ePrivacy Regulation. The proposed ePrivacy Regulation was formally withdrawn in February 2025 after years of failed negotiations. The current ePrivacy Directive (2002/58/EC, as amended by 2009/136/EC) remains in force indefinitely. Do not plan for a regulatory change that is not coming.
⚠️ Edge Cases
Section titled “⚠️ Edge Cases”CMP fails to load. If the CMP JavaScript is blocked (ad blocker, CDN failure), UIAF has no consent signal. Default: T3. Never assume consent when the CMP is expected but absent.
Consent on page 2. User navigates before interacting with the dialog. Each page load initializes at T3. On consent grant, UIAF transitions to T1/T2 and captures attribution from the current page’s context. Page 1’s UTMs are NOT recoverable — pre-consent server-side capture is scoped to the page where it runs (see Approach B scope limitation). This is expected data loss in a consent-first architecture.
Consent then refresh. CMP sets its consent cookie. On refresh, UIAF reads it and initializes directly at the consented tier. No transition logic needed.
Consent varies by subdomain. UIAF reads consent independently per page load. The UID can be shared cross-subdomain (02-identity-management.md), but attribution follows local consent state.