Consent-based, long-lived first-party ID cookie for privacy-friendly user tracking in restrictive environments.
Custom User ID Cookie (CUIC) is a solution specifically designed for tracking setups built on Google Tag Manager (GTM). It enables the generation and persistence of a random user ID across sessions via a secure, first-party HTTP-only cookie. The ID is generated client-side in GTM (based on user consent, if applicable) and made available immediately — even before the cookie is set — for GA4 tracking and other downstream integrations.
CUIC is fully compatible with ITP/ETP restrictions and enables stable cross-session identification in Safari and Firefox. It supports the use of user_id in both client- and server-side tracking architectures.
All configuration of key behaviors — such as cookie name, expiration, refresh policy, and visibility — is managed directly in GTM. No changes to the PHP files are required during setup, maintenance, or testing.
- Persistent first-party, secure, HTTP-only cookie (SameSite=Lax)
- ID available on first pageview – usable before the cookie is stored
- Fully consent-controllable via GTM
- Compatible with Safari/Firefox ITP/ETP restrictions
- HTTP-only cookie value accessible via GTM data layer
- Entire behavior (refresh, name, expiration) controlled via GTM
- Optional fallback via
if XHR is blocked
- Ideal for GA4 user_id and server-side tracking setups
🔍 Problem | ✅ CUIC Solution |
---|---|
1st-party, long-lived cookie required, but: only valid if set server-side with secure, same-origin conditions |
CUIC sets the cookie server-side on the main domain using PHP — not via JavaScript |
Consent is required before setting any ID, including via server | CUIC integrates seamlessly with GTM consent triggers, ensuring privacy compliance |
GA4 and server must use the same ID immediately, even before the cookie exists | CUIC uses a central variable (jsUserIdResolve ) to generate or reuse the ID and send it simultaneously to GA4 and the cookie handler |
On subsequent pageviews, the HttpOnly cookie is not accessible via JS, and the ID must still be available in GTM | CUIC exposes the cookie value via a PHP dataLayer snippet (cuic_datalayer-snippet.php ), making the ID available before GTM loads |
Multiple entry points / pageviews must not generate multiple IDs | CUIC ensures consistent logic in both GTM and PHP: existing values are always reused if present (in dataLayer or cookies) |
Safari and Firefox block or shorten JS-set cookies | CUIC fully complies with Safari/Firefox ITP: 1st party, secure, SameSite=Lax, server-set, HttpOnly |
XHR requests may fail due to ad blockers or ITP | CUIC includes an automatic fallback using an image GET request if the main XHR fails |
The diagram below illustrates the CUIC cookie lifecycle across first and subsequent pageviews:
-
cuic_controller.html
GTM Custom HTML tag that sends the ID to the server-side cookie handler – includes XHR logic with fallback image request
→ Triggers cookie creation/update after consent; configurable via GTM variables -
cuic_cookie-handler.php
Server-side handler that sets the cookie via POST or GET – supports dynamic config via GTM
→ Used by the controller tag to persist the ID as a secure, SameSite, HTTP-only cookie -
cuic_datalayer-snippet.php
PHP snippet which exposes the cookie value to thedataLayer
via PHP in non-cached environments – required when usingHttpOnly
cookies
→ Ensures ID is available on first pageload -
cuic_datalayer-snippet_cached.php
JavaScript-based alternative for use on fully cached pages (e.g. WordPress with page caching)
→ Loaded via<script src="/...">
; executes server-side, delivers JS response -
jsUserIdResolve.js
Central GTM variable that checks for an existing value in the dataLayer (from cookie snippet or previous tag push) or creates a new ID on first pageview
→ Ensures consistent value at all times
-
Deploy the server scripts to your main domain (same origin as your GTM container):
/cuic_cookie-handler.php
→ receives the Custom User ID and sets thetkncstm
cookie (via POST or GET)/cuic_datalayer-snippet.php
→ for non-cached environments
→ inject directly into the HTML<head>
via PHP, before the GTM container
→ ensures the Custom User ID is available synchronously at page render time
or alternatively:/cuic_datalayer-snippet_cached.php
→ for cached or static environments (e.g. full-page caching, CDNs)
→ embed via<script src="/cuic_datalayer-snippet_cached.php">
in the<head>
→ returns a dynamic JavaScript response with the dataLayer push, bypassing HTML caching
-
Create GTM variables:
- Add
jsUserIdResolve.js
as a Custom JavaScript Variable - Name it
jsUserIdResolved
or similar
- Add
-
Add the GTM tag:
- Use
cuic_controller.html
as a Custom HTML Tag - Adjust path to
/cuic_cookie-handler.php
if needed - Reference the
jsUserIdResolved
variable in the tag - Trigger only after valid consent
- Use
-
Make the ID available to GA4:
- Use
{{jsUserIdResolved}}
asuser_id
in your GA4 Config and Events
- Use
-
Customize behavior via GTM:
- All cookie parameters (
name
,refresh
,maxAge
,httpOnly
) are configurable via inline variables in the tag - No PHP edits required
- All cookie parameters (
- CUIC should only run after user consent (fully controlled via GTM)
- No personally identifiable information (PII) is stored
- No fingerprinting, no localStorage
- Fully compliant with ITP, ETP, GDPR, and ePrivacy standards
CUIC makes it easy to test different cookie configurations (e.g. name, expiration, visibility, refresh behavior) without modifying any server-side code. Just duplicate the GTM tag (cuic_controller.html
) and adjust the inline config:
cn
: cookie name (e.g.tkncstm_test
)refresh
:1
= extend on every visit,0
= set oncehttpOnly
:1
= HTTP-only (default),0
= JS-readablemaxAge
: cookie lifetime in seconds (e.g.3600
for 1 hour)
You can run multiple variants in parallel using different GTM tags and triggers — all handled by the same cuic_cookie-handler.php
on the server.
MIT – see LICENSE
/ MEDIAFAKTUR – Marketing Performance Precision, https://mediafaktur.marketing
Florian Pankarter, fp@mediafaktur.marketing