Skip to main content

Privacy

How visitors are counted

The mechanism, precisely, for each of the three modes.

The mechanism, precisely, for each of the three modes. This is the page for someone who wants to check the claim rather than take it.

The user-facing version, with the accuracy trade-offs, is Identity and cookieless mode.

First-party cookie — the default

session_id = SHA-256( "cookie" ‖ site_id ‖ visitor_id )   → first 32 hex characters

Where visitor_id is a random UUID the script generates once and stores in the ts_vid cookie on your domain.

Three properties:

The stored value is not the cookie. The cookie's raw value never reaches a column. What is stored is the digest, so a caller can assert an opaque identity but cannot choose the identifier that gets written.

The site id is inside the hash. The same browser on two customers' sites produces two unrelated identifiers with no computable relationship between them. There is no cross-site graph to build.

There is no salt in this one, deliberately. A salted digest would rotate daily and destroy the persistence this mode exists to provide — which is the whole point of choosing it.

The cookie has to be written by the script rather than by us, because a Set-Cookie from our domain would be a third-party cookie and modern browsers block or partition those.

When there is no cookie

A visitor with cookies disabled sends no visitor_id. Rather than record no identity at all, that request falls through to the rotating hash below. Their views group correctly within the day; only the cross-day link is lost — which is exactly what refusing storage asked for.

Rotating hash

session_id = SHA-256( daily_salt ‖ ip ‖ user_agent ‖ site_id )   → first 32 hex characters

The IP is consumed, not stored. It enters as an argument and leaves as a digest. It is never written to a column, a log line, an error message or a metric label.

The salt rotates every 24 hours (UTC), and the old one is deleted. This is what makes the identifier genuinely ephemeral rather than merely obscured: once a day's salt is gone, no amount of after-the-fact access to the events table can re-derive who a session was, because the input that produced it no longer exists anywhere. Not for us, not under a subpoena, not with a full database dump.

The site id is in the hash, for the same reason as above.

Components are separated by a null byte before hashing, so no two different sets of inputs can be reassembled into the same string. "ab" + "c" and "a" + "bc" must not collide into one visitor.

The consequence, which is the trade-off: after the salt rotates, the same person is a new identifier. See Identity and cookieless mode.

Strict cookieless

The same construction with three further coarsenings applied before hashing:

The IP is widened to its network. IPv4 keeps the first three octets (a /24); IPv6 keeps the first three groups (a /48). Everyone on the same subnet hashes identically.

The user agent is reduced to a class — platform plus browser engine, e.g. macos:blink — rather than the full string. Version numbers, build ids and minor variations all collapse.

The current hour is folded in, so an identifier expires within the hour rather than lasting the day.

The result identifies a rough population bucket within an hour, not a device across a day. Unique visitors are undercounted: everyone behind one office router or one carrier NAT on similar devices merges into one. Sessions crossing an hour boundary split in two.

Understating rather than overstating is the right direction of error for a mode chosen on privacy grounds, and it is the direction we chose.

Why identity is derived on the server

On every mode, the identifier is computed on our servers — the script never computes or sends one.

A script that could name its own session identifier could inflate a site's unique visitor count at will, simply by generating a new one per request. Deriving it server-side from things the client cannot freely choose is what makes the number mean anything.

The cookie mode is the narrow exception, and the exception is smaller than it looks: the client supplies an opaque UUID, and the server hashes it before storage, so the client asserts an identity without choosing the stored value. A caller can still rotate the cookie to look like many visitors — that is inherent to every cookie-based analytics product, is bounded by the per-site rate limit, and is the cost of persistence.

What "unique visitors" therefore means

ModeOne visitor is
First-party cookieOne browser, across days
Rotating hashOne browser, within one UTC day
Strict cookielessOne rough population bucket, within one hour

None of these is the same as "one person". A person with a phone and a laptop is two visitors on every mode. A person using two browsers is two visitors. A household sharing a device is one.

Every analytics product has this problem. Ours is stated rather than implied.

Excluding your own IP without storing it

Site exclusions work on a hash too:

exclusion_hash = SHA-256( "exclusion" ‖ site_id ‖ ip )

Salted with your site id rather than the daily salt, deliberately: an exclusion must survive rotation, because an owner who excludes their own address expects it to stay excluded tomorrow. Still one-way, still per-site, and the raw address is still never stored.

Was this page helpful?

Last updated August 28, 2026