Skip to content

Consent Integration


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 occurred

This section defines what “degraded” means at each level and where the gray areas are.


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.

SignalControlsDefault
analytics_storageGA4 cookies, analytics-purpose storagedenied
ad_storageAdvertising cookies, ad-purpose storagedenied
ad_user_dataSending user-provided data (hashed PII) to Google for ad matchingdenied
ad_personalizationUsing data for remarketing audiencesdenied

Set via gtag('consent', 'default', {...}) on page load, updated via gtag('consent', 'update', {...}) on consent interaction. Readable from the dataLayer array.

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.


UIAF must read consent state from whatever mechanism the site uses. Three options:

  1. Google Consent Mode state — read from dataLayer. Most universal; nearly every CMP integrates with GCM.
  2. CMP cookie directly — Cookiebot: CookieConsent (JSON with statistics, marketing booleans). OneTrust: OptanonConsent (URL-encoded groups= parameter).
  3. Custom signal — any site-specific consent mechanism.
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
}

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.


Five tiers from maximum capability to fully dormant.

Section titled “Tier 0 — Maximum Capability (No Consent Mechanism)”

No CMP is present on the site. No consent signals exist to read.

CapabilityStatus
UID persistenceFull (server cookie + client cookie + localStorage + sessionStorage)
Attribution captureFull (UTMs + all click IDs + referrer)
Endpoint payloadComplete, no fields suppressed
Consent object in payloadconsent.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.

User has accepted all consent categories (analytics + advertising). Functionally identical to T0, but with consent proof attached.

CapabilityStatus
UID persistenceFull
Attribution captureFull
Endpoint payloadComplete
Consent proofIncluded 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.

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.

CapabilityStatusReason
UID persistenceYesanalytics_storage granted = identity cookies permitted
UTM captureYesUTMs are not personal data, and analytics storage is granted
Click ID captureExcluded from UIAF payloadClick IDs are personal data used for ad matching; ad_storage denied
Referrer captureYesNot personal data
Endpoint payloadSent, 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 gclid matching from UIAF. Falls back to hashed email matching if available from the site’s own form submissions.
  • Meta CAPI: loses fbclid matching. 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.

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.

CapabilityStatusReason
Persistent UIDNoNo cookies, no localStorage for identity (ePrivacy Art 5(3))
Session IDsessionStorage (see legal risk note below)ePrivacy Art 5(3) applies to sessionStorage, but session-only functional storage has stronger “strictly necessary” arguments
Click ID captureNoPersonal data, no consent
UTM captureServer-side only (see below)
Endpoint payloadMinimal, 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.

The system is completely inactive. Nothing is stored, nothing is sent, nothing is read from the URL for UIAF purposes.

CapabilityStatus
UIDNone
Session IDNone
AttributionNone
Endpoint callNone — no HTTP request to the UIAF endpoint
Storage writesNone

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 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:

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.

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.

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.

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.


Users can modify consent preferences at any time. UIAF must handle transitions between tiers.

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.tier and _meta.attribution_completeness, send consent_update event.
  • 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. Send consent_update event 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 final consent_update event. System enters pre-consent mode.
  • Any -> T4: Delete ALL UIAF storage. Send one final consent_update event. 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.


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.

JurisdictionGPC StatusRequired UIAF Action
California (CCPA/CPRA)Legally binding opt-out of sale/sharingMinimum T2 (no ad data). T4 if interpretation is strict.
Colorado (CPA)Legally binding universal opt-outMinimum T2
Connecticut (CTDPA)Legally binding universal opt-outMinimum T2
Texas (TDPSA)Legally binding universal opt-outMinimum T2
Oregon (OCPA)Legally binding universal opt-outMinimum T2
EU/EEANot specifically referenced in GDPR/ePrivacyTreat as signal of user preference, not binding override
Other US statesVaries; check applicable lawDefault 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.


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.”

  • Sessions by tier — what percentage operates at each tier. The fundamental metric.
  • Attribution coverage rate — percentage of sessions with at least source and medium captured. 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).

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

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.



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.


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.