Back to Blog
Engineering

How Session Tracking Actually Works (Without Cookies)

Sessions are the backbone of web analytics, but most developers don't understand how they work under the hood. Here's the full technical breakdown.

MW

Marcus Webb

Head of Engineering

January 8, 20268 min read

How Session Tracking Actually Works (Without Cookies)

A session groups individual events (page views, clicks) into a single "visit" using a 30-minute inactivity timeout. Traditional analytics use cookies, which require consent banners and get blocked by ITP. SingleAnalytics uses localStorage instead , it's client-side only, not sent with HTTP requests, and not subject to the ePrivacy cookie consent requirement. Sessions are derived from events, not the other way around.

Every analytics platform talks about "sessions", but what actually is a session? How does the software decide when one session ends and another begins? And how do you track sessions without using cookies?

This article is a deep technical dive into session management in web analytics, with a focus on the cookieless approach used by SingleAnalytics.

What Is a Session?

A session represents a single visit to your website. It starts when a user arrives and ends when they leave or become inactive. Sessions group individual events (page views, clicks, form submissions) into a coherent "visit" that you can analyze as a unit.

Sessions answer questions like:

  • How long do people spend on my site?
  • How many pages do they view per visit?
  • What's the entry point and exit point of a typical visit?
  • What percentage of visits are "bounces" (single page view)?

Traditional Session Tracking (Cookies)

The traditional approach uses cookies:

  1. User arrives → Server sets a session cookie (e.g., _ga_session=abc123; expires=30m)
  2. User navigates → Cookie is sent with every request, linking events to the session
  3. User is inactive for 30 minutes → Cookie expires
  4. User returns → New cookie, new session

Problems with this approach:

  • Requires cookie consent in the EU (ePrivacy Directive)
  • Blocked by browsers' ITP (Intelligent Tracking Prevention)
  • Cleared when users delete cookies
  • Third-party cookie blocking breaks cross-domain tracking
  • Safari limits first-party cookies set via JavaScript to 7 days

Cookieless Session Tracking

SingleAnalytics uses localStorage instead of cookies. Here's exactly how it works:

Session Initialization

When the SDK loads on a page, it checks localStorage for an existing session:

// Pseudocode for session management
function getOrCreateSession() {
  const stored = localStorage.getItem('sa_session');

  if (stored) {
    const session = JSON.parse(stored);
    const lastActivity = new Date(session.lastActivity);
    const now = new Date();
    const minutesInactive = (now - lastActivity) / 1000 / 60;

    if (minutesInactive < 30) {
      // Session is still active ,  update last activity
      session.lastActivity = now.toISOString();
      localStorage.setItem('sa_session', JSON.stringify(session));
      return session.id;
    }
  }

  // No session or session expired ,  create a new one
  const newSession = {
    id: generateId(),
    startedAt: new Date().toISOString(),
    lastActivity: new Date().toISOString()
  };
  localStorage.setItem('sa_session', JSON.stringify(newSession));
  return newSession.id;
}

The 30-Minute Rule

The industry-standard session timeout is 30 minutes of inactivity. Here's why:

  • Too short (5 min): A user reading a long article would start a new session when they navigate to the next page
  • Too long (2 hours): A user who leaves for lunch and comes back would be counted as the same session, inflating session duration
  • 30 minutes: Balances accuracy for both content sites (long reading sessions) and interactive apps (rapid navigation)

Why localStorage, Not Cookies?

| Aspect | Cookies | localStorage | |---|---|---| | Sent to server | Yes, with every request | No, client-side only | | Consent required | Yes (ePrivacy) | Not for functional use | | Storage limit | ~4KB per cookie | ~5-10MB | | Expiration | Can be set | Persistent until cleared | | Cross-domain | With configuration | Same-origin only | | Blocked by ITP | Increasingly | No |

The critical difference: cookies are sent to the server with every HTTP request, which is what makes them "tracking technology" under the ePrivacy Directive. localStorage is purely client-side storage: the browser never automatically sends it to any server. The SDK reads the session ID and explicitly includes it in analytics events.

User ID Persistence

In addition to sessions, the SDK maintains a persistent anonymous user ID:

function getOrCreateUserId() {
  let userId = localStorage.getItem('sa_user_id');
  if (!userId) {
    userId = 'anon_' + generateId();
    localStorage.setItem('sa_user_id', userId);
  }
  return userId;
}

This anonymous ID persists across sessions, allowing you to track returning visitors. When sa.identify() is called, this anonymous ID is linked to a known user identity.

Session Properties

Each session tracks several derived properties:

Duration

Calculated as the time between the first and last event in the session:

duration = lastEvent.timestamp - firstEvent.timestamp

Note: If a session has only one event (a bounce), the duration is 0. This is standard across all analytics platforms.

Page Views

Count of page_view events within the session.

Bounce Rate

A session is a "bounce" if it contains only one page view. The bounce rate is:

bounceRate = singlePageViewSessions / totalSessions

Landing Page and Exit Page

  • Landing page: The path of the first page_view event in the session
  • Exit page: The path of the last page_view event in the session

Traffic Source

Attribution is determined from the first event in the session:

if (utmParams present) → use UTM source/medium
else if (referrer present) → classify referrer domain
else → direct / none

Edge Cases and How We Handle Them

User Opens Multiple Tabs

The session ID is shared across all tabs via localStorage. Events from all tabs use the same session ID and update the same lastActivity timestamp. This is correct behavior: the user is in one "visit" even if they have multiple tabs open.

User Closes Browser and Returns

If the user closes the browser and returns within 30 minutes, the session continues (localStorage persists across browser restarts). If they return after 30 minutes, a new session starts.

User Clears localStorage

A new anonymous ID and session are created. This is equivalent to a brand-new visitor. There's no way to link them to their previous identity (unless they log in again and identify() is called).

Private/Incognito Mode

localStorage works in incognito mode but is cleared when the incognito window is closed. Each incognito session starts fresh.

Server-Side Session Enrichment

When events arrive at the server, the session document is created or updated:

  1. First event in a session → Create session document with traffic source, landing page, device info
  2. Subsequent events → Update lastActivityAt, increment event/pageview counts, update exit page
  3. Session timeout (no events for 30 min) → Mark session as inactive, calculate final duration

Debugging Sessions

If session data looks off, here are common causes:

Sessions are too short: Check if your SPA navigation is triggering page reloads (which reset the SDK). SingleAnalytics hooks into pushState and popstate to handle SPA navigation without reloads.

Sessions are too long: Make sure you're not artificially keeping sessions alive with background polling or heartbeat requests that include the session ID.

Too many sessions per user: Check if localStorage is being cleared by your application or by a privacy extension. Also check for service workers that might be interfering.

User counts don't match: Remember that the anonymous user ID is per-browser, per-device. The same person using Chrome on their laptop and Safari on their phone counts as two users until they identify() on both.

Conclusion

Session tracking is one of those things that seems simple on the surface but has significant complexity underneath. By using localStorage instead of cookies, you get accurate session tracking without the privacy and consent complications of cookie-based approaches.

The key takeaway: sessions are derived from events, not the other way around. Track events accurately, and sessions take care of themselves.


Want to see your session data in real-time? Try SingleAnalytics . events appear in your dashboard within seconds.

sessionstechnicalcookieslocalStorage

Ready to unify your analytics?

Replace GA4 and Mixpanel with one platform. Traffic intelligence, product analytics, and revenue attribution in a single workspace.

Free up to 10K events/month. No credit card required.