The same weekly visitors keep being counted as first-time users
In short
Returning users counted as new users almost never means the expiry was set wrong. It means the identifier is written by page script rather than by a response from a host you control, and browsers cap script-written storage at days while you asked for years. One cohort query over people you can already name separates the recoverable loss from the loss the browser has already decided.
Key takeaways
- Where the identifier is written from decides its real lifetime; the expiry attribute only states an intention.
- The confirming query is distinct identifiers per known account per week, split by browser family — not any report in the analytics interface.
- A ratio near 1.1 is device switching, 3 to 5 is churn between visits, above 5 means the value is being rewritten inside one visit.
- A server-set identifier recovers the browser-capped share and nothing else; private sessions and denied consent stay lost by design.
- Every downstream user count inherits the churn, so publish the modelled figure beside the raw one.
The identifier is being discarded between visits, and the reason is where it was written from rather than how long you asked it to live. A cookie set by a line of JavaScript and a cookie set by a Set-Cookie header from a host under your own domain are two different classes of storage to a current browser, and only one of them reliably survives a fortnight. Raising the expiry on the first kind changes nothing, which is why teams tune it, redeploy, and watch the same chart.
The symptom to match before reading on: a new-user rate sitting close to total users whatever campaign is running, and events per user collapsing towards 1. The browser mechanisms behind it — storage caps, referrer trimming, partitioning, delayed delivery — are separate machinery with separate mitigations, set out in what the browser does to your identifiers. This page is about measuring your own churn and choosing between three responses to it.
Prove it against the people you can already name
No analytics interface answers this, because it is built from the identifiers that are churning. The measurement has to come from a population you can identify independently — logged-in users — in one query against raw event rows.
- Join 4 completed weeks of event rows to your own authentication events, so every row carries both the pseudonymous identifier and a stable account key.
- Count distinct pseudonymous identifiers per account key per week. That ratio is the diagnosis; everything after it is attributing the ratio to a cause.
- Split by browser family, and separately by whether the session's first hit arrived from another domain you own. Those 2 splits explain most of the spread.
- Query settled partitions only. An intraday table under-reports late arrivals unevenly, so churn looks worse on some days and better on others — see yesterday's totals changing once the daily export lands.
- Repeat by proxy for logged-out traffic: distinct identifiers per hashed email captured at form submit. A biased sample, still better than an assumption.
Five ways one person becomes five users
| Cause | What the cohort query shows | How much is recoverable |
|---|---|---|
| Identifier written by page script | Churn concentrated in 1 or 2 browser families; the ratio rises with the gap between visits | Most of it. Move the write to a response header from a host on your own registrable domain |
| Cross-domain journey, no hand-off | Churn only on sessions whose first hit came from another domain you own — payment host, booking engine, separate app | Nearly all of it, and the cheapest of the 3 fixes |
| Denied consent strips the identifier | Churn tracks the denial rate almost exactly once you segment by consent state | None of the cross-visit link. A consent-scoped session identifier is the most you get |
| Private sessions and cleared storage | A small, flat share, uniform across campaigns, at roughly 1 identifier per visit | None. It is a user decision, correctly enforced |
| A genuinely large first-visit audience | Churn concentrates in no browser and no entry path, and the new-user rate moves with spend | Nothing to fix. The number is telling the truth |
The order matters because the first 2 rows are engineering problems with known fixes and the last 3 are not. Teams routinely spend a quarter on rows 3 and 4 while row 1 is still writing a cookie from a script tag and losing it every week.
Why the expiry attribute is not the lever
Browsers distinguish storage written by script from storage written by a server response, and apply shorter caps to the first. Safari's tracking prevention has capped script-written cookie lifetime to a small number of days for several releases; the figure has changed more than once, so read it from the vendor's current documentation rather than a conference slide. Chrome and Firefox differ in both number and mechanism, but the direction has been consistent across all 3 — so build against the design decision, not the figure.
An identifier you asked to live for 2 years and the browser agreed to keep for 7 days is not a 2-year identifier. It is a 7-day identifier with an optimistic attribute attached.
Server-set means something specific, and half-measures fail quietly. The host must sit under your own registrable domain, you must terminate the request yourself rather than resolve it to a vendor's infrastructure, and the cookie must arrive on the first response the visitor receives — Secure, HttpOnly where script never reads the value, SameSite set deliberately — so it exists before any measurement code runs. That is the first-party endpoint between browser and vendor, and it earns its keep even for teams with no interest in server-side conversion sending.
The hand-off across domains that nobody built
Row 2 is the easiest win and the most often missed, because the journey looks continuous to everyone inside the business. A visitor moves from the marketing site to a booking engine on a different registrable domain and the identifier does not travel. Subdomains share storage; 2 separate domains never do, whatever the brand relationship between them.
- Decorate at click time, not at render time. Attach the identifier through 1 delegated handler when the click happens — links rendered before the identifier exists, or served from a CDN cache, carry a stale value or none.
- Re-write on arrival, from the server. Read the parameter on the receiving domain, write your own identifier in the response, then issue a single 302 to strip it so no user identifier can be shared, bookmarked or indexed.
- Expect a shorter lifetime on the far side. Some browsers treat an identifier arriving by link decoration as a weaker signal, so measure the far-domain ratio separately.
- Check consent travels with it. An identifier crossing a domain boundary without its consent state arrives as an event nobody can lawfully act on — the field is defined in consent as a field on every event.
The part you cannot engineer away
There is a ceiling, and stating it up front stops a 6-month project being judged against an impossible target. Compute it, do not guess it.
- Take the share of sessions arriving in browsers that cap script-written storage. Your cohort query already has the split.
- Take the share of returning visits whose gap exceeds that cap. A weekly buyer sits inside most caps; a quarterly one sits outside all of them.
- Multiply the 2. That product is the loss a server-set identifier actually recovers, and it is the number the rollout should be judged against.
- Treat the residual as fixed. Private sessions, cleared storage, denied consent and new devices are not addressable by any change to your stack.
The residual has to be modelled, and modelled honestly: an estimate labelled as an estimate, in the derived layer of the warehouse beside the observed count, never overwriting it. The layering that keeps the two apart is in turning raw event rows into a model people can query, and why a modelled figure can never be reconciled row by row is in why modelled conversions never reconcile to rows.
Cold identifiers are not only a reporting problem. A bidder answering a request for a user it has never seen, with no profile to look up and a hard response budget, is solving the same shortage under a stopwatch — the constraint behind designing a bid response path inside the timeout. Anything reasoning over user counts inherits it too: a pacing agent reading a churn-inflated new-user series reports demand that does not exist, so the AI agents and automation work we do over marketing data reads the modelled user table, not the raw identifier count. That join table is personal data by any reading, which argues for modelling inside your own boundary — as in private and on-premises model deployment.
Where the tree ends: set it server-side, hand it off, or model the gap
- Set the identifier server-side when churn concentrates in a browser family and the ratio rises with the gap between visits. Largest fix, highest cost: infrastructure on the request path, and a rollout following the one-event-at-a-time discipline of moving one conversion server-side first.
- Build the cross-domain hand-off when churn concentrates on sessions that started on another domain you own. Days of work, no new infrastructure, often the larger share of the loss wherever checkout sits on its own domain.
- Accept and model when the residual is private browsing, denied consent or a genuinely new audience. Write the ceiling down, publish it beside the new-user rate, and stop paying for attempts to breach it.
Re-run the cohort query a fortnight later and compare it with the number you recorded before. A rollout that moves the ratio from 4.2 to 1.6 in one browser family has worked even if the headline new-user rate barely moves, because the headline is diluted by the population you were never going to recover. The rest of this silo sits under tracking, consent and event pipelines, part of our marketing and advertising practice.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
Why does analytics count the same visitor as a new user every week?
Because the identifier stored in their browser was gone before they came back. The commonest reason is that it was written by page script, which browsers cap at days rather than the months the expiry attribute asked for. The other 4 reasons are a cross-domain journey with no hand-off, a denied consent state that strips the identifier, private browsing, and a genuinely large first-visit audience — and a cohort query over logged-in users tells you which mix you have.
Does increasing the cookie expiry fix returning users showing as new?
No, not when the cookie is written by script — the browser caps it regardless of what you asked for. The expiry attribute is a request, not a guarantee, and the cap applied to script-written storage is shorter than most reporting windows. Changing where the cookie is written from, so it arrives in a response header from a host on your own domain, is the change that moves the number.
How much of the churn can a server-set identifier actually recover?
The share of traffic that is both in a capped browser and returns after the cap expires — no more. Multiply the browser-family share by the share of returning visits whose gap exceeds the cap, and that is the recoverable portion. Private sessions, cleared storage and denied consent sit outside it permanently, so a rollout judged against total new-user rate will look like a failure even when it worked.
Do logged-in users solve the problem?
They solve it for the population that logs in, which is rarely the population you buy media against. An account key is a durable identity and should be the backbone of the user model, but most paid traffic never authenticates, so the pseudonymous identifier still has to work at the top of the funnel. Use the logged-in cohort as the measuring instrument rather than as the fix.
- identifiers
- first-party cookies
- measurement
- diagnostics
The work behind this page
Builds from our portfolio that this page draws on.
PipelineIQ
An AI SDR platform that scores every lead for fit, runs multichannel sequences across email, LinkedIn and call, drafts the replies, and books the meeting.
Sales AIChurn Radar
An AI customer-success platform that flags at-risk B2B accounts before they churn and prescribes the save-play to run.
Customer SuccessRead next
- The tagging server: a first-party endpoint that owns the payloadA tagging server is not a proxy. It parses one first-party event, adds and strips fields, and emits a vendor-shaped request per destination from your infrastructure.definition
- A slice of events lands before the visitor has answered the bannerEvents arriving with an absent consent field are not a compliance abstraction. They are a race between two scripts, and the race has a rate you can measure this afternoon.diagnostic
- Click identifiers: the URL parameters that let a server-sent conversion find the ad that caused itA campaign tag describes where traffic came from. A click identifier is the key that joins a sale back to a specific click — and only one of the two is load-bearing.definition
- Consent state: a typed field on each event, not a switch on the pageThe pageview before the banner answer and the purchase after it are both correct, and they carry different consent values. That only works if consent travels per event.definition
- Conversions started counting twice the week server-side sending was switched onA step change in reported conversions on the rollout date with flat order volume means deduplication failed. Five candidates, and one report that names which.diagnostic
- Event ID: the value that lets two systems agree a conversion happened onceThe same identifier, the same conversion, both transports, inside the platform's window. Four generation strategies, and the guarantee each one keeps or breaks.definition
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.
Start the conversation