Founders ask for “unique visitors” the way they ask for “uptime”: a single number that sounds objective. In a cookieless, sessionStorage-first tracker, the honest unit is usually session: a browser tab’s continuity on your origin until storage is cleared or the tab ends. That choice powers much of the cookieless analytics guides story, and it comes with tradeoffs you should name in internal docs, not hide in footnotes.
Start with analytics without a cookie banner for EU SaaS. KiboData stores a generated session_id in sessionStorage (key fm_sid in the shipped script), sends batched pageviews to ingest, and never touches document.cookie for analytics. No magic, just explicit engineering limits.
How sessionStorage session IDs are created
On the first executed pageview in a tab:
- The script reads
sessionStoragefor an existing session key. - If missing, it generates a random string (prefixed for readability) and writes it back.
- Each event in the batch includes
session_id,path,url,referrer, and UTM fields parsed from the query string on that load.
There is no cross-tab sync. Opening pricing in a new tab from a middle-click creates two sessions from one human intent. That is not a implementation bug; it is how sessionStorage is specified.
Compare to a first-party cookie with a 365-day expiry: the cookie model optimizes for persistent recognition. The sessionStorage model optimizes for minimal retention in the browser and a clear story, “we only remember you for this visit.”
Defining “visitor” for your team
Before you debate tools, write one internal sentence:
> We count sessions as our visitor unit unless the user is logged in and we attach account_id server-side.
Share that sentence with anyone who reads dashboards. Otherwise, someone will compare your number to Plausible’s “unique visitors” or GA4’s “users” and conclude your product is broken.
| Unit | What it approximates | Inflates when… | Deflates when… |
|---|---|---|---|
| Session (sessionStorage) | One tab journey | Many tabs, refresh-heavy workflows | Long single-tab research |
| Cookie-based visitor | Same browser over days | Shared devices | Cookie clears, ITP |
| Logged-in user | Account | Team seats | Anonymous browsing before signup |
For indie SaaS marketing sites, session is often the right compromise for EU privacy posture, as long as you do not market it as MAU.
UTM persistence inside a session
A common failure mode: visitor lands with ?utm_source=newsletter, navigates to /pricing where the query string is clean, and your dashboard shows direct traffic.
SessionStorage-backed trackers fix attribution reads by copying UTMs into storage on first load and re-attaching them to subsequent pageviews in the same tab. The visible URL can stay pretty; the event payload still carries campaign context.
Limits:
- New session loses UTMs unless the new entry URL includes tags again (e.g. email link with UTMs on every CTA, usually fine).
- Cross-subdomain navigation only works if the script runs on both hosts and storage is shared per origin rules (
wwwvs apex is painful, pick one canonical host). - Stripe Checkout redirect can end the marketing tab’s session story; pass metadata or use return URLs that preserve your join keys. The Stripe revenue guides covers webhook-first truth.
Mobile Safari and in-app browsers
Mobile browsers are where pretty session math dies politely.
- Private mode may throw on
sessionStorageaccess; robust scripts fall back to ephemeral in-memory IDs for that page load only, session continuity degrades to per-page. - In-app browsers (Twitter, LinkedIn, Instagram) sometimes isolate storage aggressively; a user who opens your site in-app then switches to Safari is two sessions minimum.
- ITP targets cookies more than sessionStorage, but Apple’s policies change; do not build five-year forecasts on vendor blog posts from 2023.
Operational advice: when you launch on social, compare session counts week over week, not to desktop baseline absolute numbers. Segment referrer contains t.co or linkedin if you need sanity checks.
Joining sessions to revenue
Cookieless analytics still needs a bridge to money:
- Webhook
checkout_completedwith amount, currency, and optional customer email hash (if you chose to send it). - Time-window join: session had a pricing pageview within N hours before webhook, good for narratives, fuzzy for accounting.
- Explicit metadata: hidden fields or Checkout Session metadata populated from sessionStorage before redirect, strongest when redirect flows cooperate.
KiboData expects webhook-first revenue lines in the same project as traffic. Pageview-only “conversion” counts lie the moment someone pays from a bookmarked Stripe link.
Do not double-count: one checkout, one revenue event. Refunds should decrement or annotate if your finance brain cares, document policy.
New vs returning, what sessionStorage can and cannot say
You can set a second sessionStorage flag: seen_before=1 after the first visit ever in that browser profile (still tab-scoped unless you use localStorage, which is a different privacy conversation). Some teams use localStorage for a coarse “returning” bit; KiboData’s default tracker does not: staying cookieless and minimizing cross-visit persistence.
Without localStorage or cookies:
- “Returning visitor” in the marketing sense (came back next week) is unknown unless they log in.
- “Returning within the same session” (pricing → docs → pricing) is visible in path tools.
Be honest in blog posts and investor updates: growth loops are validated with cohort signups and revenue, not with precision return-visitor rates from marketing analytics alone.
Session count vs pageview count
Pageviews always exceed sessions when people read multiple pages, good. If pageviews ≈ sessions, either traffic is single-page (ads to a squeeze page) or tracking is broken (SPA navigations not firing). For Next.js and similar SPAs, ensure route changes emit pageviews or equivalent events.
Founders sometimes panic when sessions drop after fixing double-fire bugs. Celebrate: you removed noise.
Privacy properties engineers care about
sessionStorage clears when the tab closes (modulo browser crash recovery edge cases). It is not sent automatically to other sites. It is readable by any script on your origin, so XSS on your marketing site is still catastrophic; analytics choice does not fix injection.
No cookies means:
- Consent tools that scan for
_gamay show a cleaner report, verify with your scanner, not assumptions. - Subresource Integrity and a tight CSP still matter for the analytics script supply chain.
Server-side, retention policy dominates risk: delete or aggregate raw events on a schedule you can explain to a customer DPA questionnaire.
When to choose cookies anyway (rare for this audience)
Consider a first-party cookie session only if:
- You must recognize returning visitors across weeks without login, and counsel approves.
- You operate a multi-step funnel across subdomains you control and cannot proxy under one origin.
- You integrate a tool that requires cookie IDs (enterprise AB test suites).
Most bootstrapped EU SaaS teams should exhaust sessionStorage + webhooks + UTMs before reopening the cookie jar.
Testing checklist before you trust dashboards
- Land with UTMs, click three internal links, confirm campaign still attributes in ingest.
- Middle-click open two product pages, expect two sessions.
- Complete a test purchase; confirm revenue event and prior session share a project date range.
- Load site in Safari private window; confirm events still arrive (maybe with degraded session stickiness).
- Block your ingest domain in uBlock; confirm you understand undercount bias (technical buyers do this).
Document results in your internal wiki so the next contractor does not “fix” session splitting by adding a year-long cookie without review.
Talking to customers about the model
EU B2B buyers increasingly ask subprocessors and data location. Your answer should mention:
- Session identifiers are random, not email addresses.
- No cross-site tracking pixels in the default KiboData snippet.
- Events are site-scoped; another customer’s traffic never mixes in your dashboard.
Avoid saying “anonymous” if you store full URLs with query params that might contain tokens, strip or hash at ingest if needed.
Related guides
The main guide frames banner and policy conversations. This article is the mechanics layer. For regulatory and copy habits in France, see France indie SaaS practical analytics notes. For metrics you should stop promising, see what you cannot measure without cross-site IDs.
Practical defaults for KiboData users
- Treat sessions as the visitor line in RPV math (revenue ÷ sessions over the window).
- Pin launch weeks in the dashboard when comparing before/after pricing, session definitions make single-day comparisons noisy.
- Use
window.KiboData.track('signup')(or legacy alias) on meaningful product events; do not spam custom events for every button hover.
sessionStorage visitor counting is a deliberate downgrade from surveillance-grade identity, and for indie EU SaaS, that downgrade is often a feature. Name the tradeoffs, join webhooks honestly, and make decisions on trends you can reproduce next month.