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)
- Optional JS-readable mirror cookie for client-side JavaScript access
- 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, mirror cookie) 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 thedataLayervia PHP in non-cached environments – required when usingHttpOnlycookies
→ 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 thetkncstmcookie (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.jsas a Custom JavaScript Variable - Name it
jsUserIdResolvedor similar
- Add
-
Add the GTM tag:
- Use
cuic_controller.htmlas a Custom HTML Tag - Adjust path to
/cuic_cookie-handler.phpif needed - Reference the
jsUserIdResolvedvariable in the tag - Trigger only after valid consent
- Use
-
Make the ID available to GA4:
- Use
{{jsUserIdResolved}}asuser_idin 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 - Optional: Enable JS-readable mirror cookie by setting
addJsCookie = "1"(see below) - 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.3600for 1 hour)addJsCookie:1= enable JS-readable mirror cookie,0= disabled (default)jsCookieName: name for the mirror cookie (default:tkncstm_m)
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.
CUIC optionally supports a JS-readable mirror cookie that runs alongside the primary HTTP-only cookie. This enables client-side JavaScript access to the user ID when needed (e.g., for custom analytics, A/B testing, or client-side personalization) while maintaining the security benefits of an HTTP-only primary cookie.
How it works:
- When
addJsCookie = "1"is set, both cookies are created together on the first page load - The mirror cookie always mirrors the value of the primary cookie
- If the primary cookie exists, the mirror cookie synchronizes with it
- Both cookies share the same expiry timestamp (prevents drift)
- The mirror cookie is not HTTP-only, allowing JavaScript to read it via
document.cookie
Use cases:
- Stape Cookie Keeper: Enables Stape's cookie synchronization to access the user ID via JavaScript for cross-domain and cross-platform consistency
- Client-side JavaScript needs access to the user ID
- A/B testing frameworks that require cookie-based user identification
- Custom analytics scripts that run before GTM loads
- Maintaining backward compatibility with legacy JavaScript code
Configuration:
In cuic_controller.html, set:
var addJsCookie = "1"; // Enable mirror cookie
var jsCookieName = "tkncstm_m"; // Mirror cookie name (optional, defaults to "tkncstm_m")The mirror cookie will automatically:
- Be created alongside the primary cookie on first load
- Stay synchronized with the primary cookie value on subsequent pageviews
- Share the same expiration time as the primary cookie
MIT – see LICENSE
/ MEDIAFAKTUR – Marketing Performance Precision, https://mediafaktur.marketing
Florian Pankarter, [email protected]
