Cookieless sessions
3x does not need cookies or device storage to count sessions. Before consent the browser keeps its ids in memory only; the server links the requests of one visitor with a hash that changes every day and cannot be recomputed later.
In the browser
- Every page load creates a random session id (UUID v4) and a random id per pageview. Both live in memory. Navigation inside a single-page app keeps the session id.
- The session id rotates after 30 minutes without a send.
- A tab hidden for more than 30 minutes starts a new pageview (
nav = resume) and a new session when it becomes visible again. A restore from the back/forward cache starts a new pageview withnav = back_forward. - Pageviews are finalized before they turn 24 hours old.
- After consent the session id is kept in
sessionStorage(<prefix>tsid), so a full reload within 30 minutes continues the session in the browser itself.
On the server
Each batch carries the client session id and a flag fresh (generated on this page load). The
server resolves the session of every new pageview, oldest first:
- Personal pages (for example personalized QR-letter URLs, configured as personal paths) get an isolated session with a random id – no hash, no referrer, no attribution, hour-truncated times.
- Alias: a session of the same client session id that is still alive (last seen < 30 min) and
has the same campaign → stitch
memory(SPA navigation) orstorage(restored after consent). - Hash: only when the id is fresh – the latest session with the same visitor hash, alive,
started less than a day ago, same campaign → stitch
hash. - Otherwise a new session (id = the client id unless taken).
A new campaign starts a new session: when the URL carries attribution (UTM parameters or click ids) that differs on any of the 13 keys from the session's entry attribution.
The visitor hash
visitor_hash = first 32 hex of sha256(daily_salt ‖ ip ‖ "\n" ‖ user_agent ‖ "\n" ‖ project_id)- The salt is random, per project and per calendar day in the project time zone. Salts of past days are deleted on every salt access and by the cleanup job, so yesterday's hashes cannot be recomputed – not even by the operator.
- The scope is the project: all hosts of a project stitch, but a visitor can never be linked across projects or tenants.
- IP and user agent are used for the hash and for rate limits only and are never stored. The user agent is reduced to device class, browser family and OS family.
- The hash is computed only for batches that contain a non-personal pageview.
Visitors
Visitors are an estimate: distinct (local day, visitor hash) pairs plus sessions without a hash.
A visitor who returns on another day counts again – that is the price of not storing identifiers.
Behind proxies and CDNs
Stitching needs the visitor's real IP. If events reach the API through a proxy that does not forward
it, all visitors look alike and are merged. Use the first-party proxy, which forwards
the IP with a proxy key, or make sure the API sees the client address
(X3_TRUSTED_IP_HEADERS). The dashboard warns when
more than 30 % of a day's sessions share one visitor hash.
Stitch kinds in reports
Every session records how it was linked (new, memory, storage, hash) – filter with
stitch= or break down by stitch to see how much of your traffic relies on hashing.
Test traffic and opt-out
?x3_test=1anywhere in a document's lifetime marks all its batches as test traffic. Reports exclude it unlessinclude_test=true.?x3_notrack=1stores an opt-out flag in this browser (localStorage, the one write allowed without consent because the user explicitly asked for it);?x3_notrack=0removes it. The dashboard's exclude own devices box has both links.- Parameter names are configurable per project (
testParams,notrackParam).