Identity and consent
Everything on this page is client-side behaviour, shared by every SDK binding. For what the backend does with the identifiers once they arrive, see Identity resolution.
What the SDK stores
Keys are prefixed with storagePrefix, which defaults to __ut_. Cookies are
written SameSite=Lax, with Secure per the secureCookies option. Reads try
cookie first, then localStorage, then sessionStorage — so clearing one layer
doesn’t lose the visitor.
The day figures track anonymousIdTTL and attributionTTL, the minutes track
sessionTimeout — see Configuration.
The /id call and Safari ITP
On init the SDK issues GET /id and adopts the anonymous_id the server
returns, alongside an httpOnly cookie the server sets for 365 days.
The reason is Safari’s Intelligent Tracking Prevention: a cookie written by JavaScript is capped at roughly 7 days. A visitor who returns after a fortnight looks brand new, breaking the link between the ad click and the purchase. A cookie set by the server in an HTTP response header isn’t subject to that cap, so the server-issued id survives.
The fetch is non-blocking. Events sent before it resolves use the client-generated id, and the backend stitches the two together during identity resolution. Nothing is dropped waiting for it.
This only helps when the collector is same-site with your pages. Serving it from your own subdomain via first-party domains is what makes the server cookie first-party rather than third-party.
Cross-domain linking
Cookies can’t cross registrable domains. To carry one visitor between
example.com and example-checkout.com, append the linker parameter to outbound
links:
getLinkerParam() returns _ut=<url-encoded anonymous_id>. On the receiving
page the SDK reads _ut from the URL — and then applies one rule that matters:
An inbound _ut is adopted only when the visitor has no stored id yet. A
returning visitor’s id is never overridden.
Without that rule, anyone could craft a link carrying their own id, share it, and merge every recipient into one person — corrupting the identity graph and, since identity drives conversion attribution, your reporting with it. Seeding a fresh visitor is safe; overwriting a known one is not.
_ut is always stripped from the address bar, adopted or not, so a third
party’s id can’t bleed into the captured page URL and get logged as page context.
For subdomains of one site you don’t need the linker at all — set
cookieDomain: '.example.com' and the cookie is shared.
Click ID capture
Click IDs arrive by two routes, and the SDK treats them differently.
From the URL, on any page load — 11 parameters: fbclid, gclid, gbraid,
wbraid, ttclid, li_fat_id, msclkid, epik, rdt_cid, sc_click_id,
twclid. These are persisted for attributionTTL days and merged, not
replaced. A visitor who arrives from Google and later returns from Meta keeps
both, because the server can only backfill a click it still knows about.
From platform cookies, re-read fresh on every single event — fbp from
_fbp, fbc from _fbc, ttp from _ttp. These are written by Meta’s and
TikTok’s own pixels if you also run them. They’re read live rather than cached
because the platform pixel can rewrite them mid-session, and a stale value is
worse than none: it lowers your match quality while looking like a signal.
Only 4 of the 9 platforms Pivolio recognizes are delivery destinations — Meta, Google Ads, TikTok and Reddit. The other click IDs are captured purely for attribution.
UTM parameters
utm_source, utm_medium, utm_campaign, utm_content and utm_term map to
campaign.source, .medium, .name, .content and .term.
Unlike click IDs, a new campaign replaces the stored one. The asymmetry is deliberate. Click IDs are per-platform credentials — losing one loses a delivery match. A campaign is a single answer to “how did this visitor last get here”, and merging two campaigns produces a chimera that belongs to neither.
Consent
setConsent() takes a Google Consent Mode v2 object. Three categories change
behaviour, and unset is treated as not-denied:
ad_storage: 'denied'— the event is not sent at all.ad_user_data: 'denied'— PII traits are stripped; the event still sends.ad_personalization: 'denied'— ad click IDs are stripped.
Denying ad_storage also immediately discards anything still buffered, so
events captured before the visitor declined are never flushed afterwards.
Three independent kill switches
They are not variations of one setting, and turning one off doesn’t turn the others off.
respectDNT— init-time. When the browser sends Do Not Track and this is enabled, the tracker never initializes.optOut()— durable and persisted. Survives reloads via theoptoutkey. Reverse it withoptIn(); query it withhasOptedOut().ad_storage: 'denied'— runtime, and not persisted. Re-apply it on each page load from your own consent store.
An opted-out visitor is fully inert on later loads: no /id call, no auto page
view, no events. The local anonymous_id is kept — it backs the public getters —
but nothing ever leaves the device.
Next steps
- Configuration — TTLs, cookie domain,
respectDNT. - Identity resolution — what the backend does next.
- Attribution — how a click becomes credit.