Every analytics vendor says cookieless now. Very few explain what their implementation actually does, and almost none tell you what it cannot do. This post explains ours, including the part that does not work, because if you are going to trust numbers from a tool, you should know exactly how those numbers are made.
Why we wanted nothing on the device
When we started building Zenovay, we made one decision early: the default tracking mode stores nothing in the browser. No cookies, no localStorage, no IndexedDB, nothing that survives the tab.
Not because storage is evil. Because storage is a commitment. The moment you write an identifier to someone’s device, you own a pile of questions about consent, retention, and deletion, and your users own a banner. We wanted the default to be the mode where those questions do not exist, because there is nothing to delete.
That decision sounds clean until you try to answer the most basic question in analytics: is this pageview from the same person as the one five minutes ago? Without a stored identifier, the honest answer is that you have to guess. So the real design question is: how do you guess well, and how do you make the guess expire?
The identity function
Here is the whole mechanism. When an event arrives at our edge, the server computes:
visitor_key = sha256(
ip_subnet // not the full IP
+ user_agent // the browser's own header
+ daily_salt // rotates at midnight UTC
)
Sessions are a 30 minute window on top of that key. The hash happens server side, on Cloudflare Workers, before anything is written. The tracker itself sends no identifier because it does not have one. There is nothing on the client to inspect, block, or clear, because there is nothing there.
Three ingredients, each chosen for a reason.
The IP subnet, not the full IP. Using the full address would make the key more precise and more invasive at the same time. The subnet is deliberately blurry. It also absorbs some of the churn from mobile networks, where the last octet can change between requests while the subnet stays put.
The user agent. It separates the two people behind one home router better than nothing does. It is a weak signal on its own, and that is fine. It is one input of three.
The daily salt. This is the entire privacy argument in one variable. The salt rotates at midnight UTC and the old one is discarded. Once it is gone, yesterday’s keys cannot be recomputed by anyone, including us. We could not rebuild a person’s week if a court asked us to. The identifier does not need to be deleted because it expires by construction.
The flaw
That salt rotation is also a flaw, and we want to be precise about it instead of hiding it in a footnote.
At midnight UTC, every visitor key on the planet changes. The same person, on the same network, in the same browser, becomes a brand new visitor. Which means:
- Unique visitor counts across multi day ranges are overcounted. A daily reader shows up as seven people in a weekly view.
- A returning visitor rate across days is not something we can honestly compute in cookieless mode, so we do not pretend to.
- A five day consideration journey, where someone reads your pricing page on Monday and signs up on Friday, looks like two unrelated people.

If your business depends on cross day visitor stability, this design will annoy you. That is not a bug report we can fix. It is the price of the property described above, and we think the price is worth stating plainly.
What still works, and why revenue attribution survives
Everything inside a day works normally: sessions, journeys, funnels, live view, and channel attribution for the visit that is happening right now.
The interesting case is revenue. If identity dies at midnight, how can we tell you which channel produced a paying customer three weeks after the first visit?
Because at conversion, the guessing stops. When someone signs up or pays, they identify themselves to your product, and your product can tell us. From that moment the journey is anchored to a real account, and the attribution question becomes answerable again, backwards from the conversion. The anonymous phase stays anonymous. The paying phase is precise. We think that is the right place for the boundary: precision arrives exactly when a person chooses a relationship with you.
What we rejected
Heavier fingerprinting. Canvas entropy, font enumeration, audio context quirks. It would make the key more stable and it would make us a surveillance company with a nicer landing page. No.
A weekly salt. Better continuity, softer privacy story. Once you rotate weekly, you are one config change away from monthly, and one more from never. Daily is a bright line and bright lines survive product pressure.
A localStorage fallback. Storing a key only when some heuristic decides it is fine would mean the honest sentence on our landing page needs an asterisk. The sentence is worth more than the continuity.
Collisions, because honesty goes both ways
The flaw above splits one person into many. 
The opposite failure exists too: two people in the same office, on the same subnet, with identical browser versions, can merge into one visitor for a day. We accept that. Under counting and over counting are both distortions, but they are bounded, they expire daily, and neither of them requires writing to anyone’s device.
If you need cross day stability anyway
Cookieless is the default, not a dogma. Every site in Zenovay has a toggle. Turn cookieless mode off and tracking uses a first party cookie, visitors stay stable across days, and the consent obligations that come with that are yours to handle, as they would be with any tool. The default stays storage free because defaults are what most people ship.
The point
We wrote this because most cookieless claims are marketing sentences, and marketing sentences do not have failure modes. Real mechanisms do. Ours splits people at midnight UTC, merges some office colleagues, and refuses to answer questions it cannot answer honestly. Knowing that, you can decide what our numbers mean.
If you have built identity systems and see a sharper tradeoff we missed, we genuinely want to hear it.



