Grafana Cloud

Session tracking

Note

Session tracking requires Faro Web SDK version 1.3.6 or greater. In order to visualize your data in the Grafana Cloud Frontend Observability plugin, you must instrument your frontend application with at least v1.3.6.

The session tracking instrumentation reports the change of the session meta as an event. This is useful for RUM because you can track when a new session starts.

Using the session tracking instrumentation, you can correlate errors, logs, and events that occurred for a particular user during a single session using the application.

In order for the session tracking instrumentation to work correctly, the session meta should not be removed.

What is a session?

A session is defined as the time that a user spends in a Frontend App from start to finish or timeout. Sessions are either created or resumed when a user visits and uses the app.

An individual session ends after a maximum lifetime of 4 hours or after an inactivity timeout of 15 minutes. When the user returns after the timeout period elapses, the SDK automatically creates a new user session.

How session tracking works

Faro automatically creates a new session if a user visits a page, or depending on the chosen mode, picks the last session of a recurring user.

A Faro session has a max. lifetime of 4 hours and a max. inactivity time of 15 minutes.
The maximum time Faro keeps a tracked session (see below) is 45 minutes by default but can be configured.

If either of the session timeouts is reached, Faro creates a new session and updates the session meta object with the new session id as well as the previous session id. The latter gets added via the previousSession attribute.
Every time a new session is created Faro calls the optional onSessionChange callback and provides the oldSession and newSession meta as parameters. The oldSession meta is null when the initial session is created.

The Faro receiver also checks the related session of every beacon sent by Faro for its validity, that is the validity of the max. lifetime and activity-time.
If a beacons session is invalid it drops it.
The web-sdk takes care of creating new sessions if either of the aforementioned time ranges is reached, so there should never be a need for the receiver to drop beacons.

Faro attaches the x-faro-session-id custom header, with the value set to the current session-id, to each HTTP request sent to the collector URL, so that the backend can keep track of user-sessions and enforce constraints.

To help users identify lifecycle events, Faro sends different lifecycle events throughout the life of a session.

The lifecycle events are:

  • session_start: if no session is found in web-storage during the initialization phase
  • session_resume: if a valid session is found in web-storage during the initialization phase
  • session_extend: if Faro auto-creates a new session because one of the session timeouts is reached or if setSession(...) is called manually

Faro provides two modes for session tracking, which are described in the next two sections.

Tracked sessions mode

If the tracked sessions mode is enabled, Faro persists session information in the browsers Local Storage. This enables session information to outlive page reloads as well as to sync sessions between the different browsing contexts like tabs or new windows from within the same browser.

Faro also keeps the session for a configurable amount of time so it can pick up the session in case a site visitor closes the browser and returns after some time.

Note:

Faro can not differentiate session between users. So if different users use the same browser, Faro picks up the last persisted valid session. This can lead to a situation where different users share the same session Id. Faro provides multiple ways to deal with the situation, depending on your needs. See the examples at the end of the page.

Volatile sessions mode

If the volatile sessions mode is enabled, Faro stores session information in the browsers Session Storage. This means session information can outlive page reloads within the same site but it doesn’t persist the session between browsing context like other tabs or windows. Opening a new tab or window with the same site creates a new session.

After closing a tab the session is gone, which means it is not possible to have a recurring visitor to pick up on the last session.

Sampling

Though sampling is defined it is documented in a dedicated page. Refer to the Sampling documentation to learn more about Faro’s sampling capabilities and how to set up the sampler.

Configuration options

The session manager can be configured with the sessionTracking object via the Faro setup function.

It provides the following options:

OptionsDescriptionDefault value
enabledEnable or disable session managementfalse
persistentWether to use tracked or volatile sessionsfalse
sessionInitial session object, only for very special demandsundefined
maxSessionPersistenceTimeHow long to keep a session to identify a recurring visitor (tracked sessions only)15 minutes + 1 minute buffer
onSessionChangeCalled each time a new session is createdundefined
samplingRateNumber between 0 (0% of signals sent) and 1 (100% of signals sent).1
samplerCalculate a sampling decision based on other properties. Should return number between 0 and 1.undefined
generateSessionIdIf present, Faro calls this function each time it needs to create a new session id.undefined
ts
sessionTracking?: {
  enabled?: boolean;
  persistent?: boolean;
  session?: MetaSession;
  maxSessionPersistenceTime?: number;
  onSessionChange?: (oldSession: MetaSession | null, newSession: MetaSession) => void;
  samplingRate?: number;
  sampler?: (context: SamplingContext) => number;
  generateSessionId?: () => string;
};

Add the session tracking instrumentation

The following session tracking instrumentation is enabled by default. No additional configuration is required.

ts
initializeFaro({
  url: 'https://my-domain.my-tld/collect/{app-key}',
  app: {
    name: 'my-app',
  },
});

Note

If you overwrite the instrumentations array when you initialize the Grafana Faro Web SDK, you must manually include the Session tracking instrumentation.

To manually include the session instrumentation, use the following getWebInstrumentations helper function.

ts
initializeFaro({
  url: 'https://my-domain.my-tld/collect/{app-key}',
  app: {
    name: 'my-app',
  },
  instrumentations: [...getWebInstrumentations()],
});

Alternatively, if you want to fine-tune which instruments are enabled, you can use the following SessionInstrumentation class.

ts
initializeFaro({
  url: 'https://my-domain.my-tld/collect/{app-key}',
  app: {
    name: 'my-app',
  },
  instrumentations: [new SessionInstrumentation()],
});

Configure the session tracking instrumentation

ts
initializeFaro({
  ...
  sessionTracking?: {
    enabled: true;
    persistent: true;
    onSessionChange: (oldSession: MetaSession | null, newSession: MetaSession) => {
      console.log(`Session ${oldSession == null ? 'created' : 'changed'}`, {oldSession, newSession})
    } ;
  };
});
If multiple users share the same browser

If persistent sessions are activated, Faro persists the users session in the browser Local Storage. If a user opens the site, Faro selects the session from storage if it’s valid or creates a new one if not.

But if multiple users share the same browser, this may lead to a situation where user A leaves the site, then a bit later user B navigates to that site. So Faro selects the valid persisted session. This causes the user B having the same session id as user A.

Since Faro can not derive if a visitor is the same person or a new one, you need to take care of this. Faro provides some options to mitigate the issue, which are the following:

First possibility is to define a shorter maxSessionPersistenceTime (default 15 minutes + 1 minute buffer).

ts
initializeFaro({
  ...
  sessionTracking?: {
    enabled: true;
    persistent: true;
    maxSessionPersistenceTime: 1 * 60 * 1000 // 1 minute
    onSessionChange: (oldSession: MetaSession | null, newSession: MetaSession) => {
      console.log(`Session ${oldSession == null ? 'created' : 'changed'}`, {oldSession, newSession})
    } ;
  };
});

Second possibility is to delete the Session manually when you consider a session as finished.
The easiest way is to use the static method removeUserSession() on PersistentSessionsManager.
This removes the session from Local Storage and Faro creates a new one on the next page load.

ts
import { PersistentSessionsManager } from '@grafana/faro-web-sdk';

function handleSessionClose() {
  PersistentSessionsManager.removeUserSession();
}

Create custom session ids

In case a custom id scheme is needed, Faro provides the generateSessionId() function. If this function is present in the configuration object, Faro always calls this function instead of its internal sessionId generator.

ts
initializeFaro({
  ...
  sessionTracking?: {
    generateSessionId() {
      // generate and return custom sessionId here.
    }
  };
});

Sessions and iFrames

Your application might embed one or more applications in iFrames. Session behavior depends on the iFrame origin and browser security rules. These factors also affect signal correlation and session counting in Grafana Cloud.

Same-origin iFrames

Same-origin iFrames share browser storage but not the same document context.

If the parent application and iFrame content come from the same origin (same protocol, host, and port), they can share the same session through shared storage. Each context needs its own Faro initialization, but both instances read and write session data through the same localStorage or sessionStorage, even when iFrames are destroyed and recreated.

This results in:

  • Full correlation between parent and iFrame activity
  • One billed session in Grafana Cloud
  • Shared sampling decisions
  • The iFrame emits session_resume when it picks up the parent’s session from storage
  • A continuous session across iFrame reloads or swaps because the new iFrame instance resumes the existing session from storage
  • A session_resume lifecycle event emits when a newly loaded iFrame finds a valid stored session

When the iFrame loads different applications over time (for example, a persistent shell loading multiple apps), each same-origin app continues the same session as long as the session remains valid.

  • Same origin + persistent sessions (localStorage) = continuous session across iFrame reloads
  • Same origin + volatile sessions (sessionStorage) = continuous session within the same tab, but not across tabs or after tab closure

Cross-origin iFrames

Cross-origin iFrames cannot access each other’s storage or browser APIs protected by the Same-Origin Policy. If you initialize the Grafana Faro Web SDK in both places, each environment starts and maintains its own session. The session IDs don’t match and aren’t synchronized.

This results in:

  • Multiple sessions: one session for the parent frame and one separate session for each iFrame load
  • Multiple billed sessions
  • No automatic correlation between events inside the iFrame and events in the parent application
  • Session persistence varies by browser: Safari blocks third-party storage entirely, creating a new session on every iFrame reload. Chrome 115+ and Firefox 103+ use storage partitioning that allows cross-origin iFrames to persist their own sessions across reloads.

Cross-origin restrictions or Content-Security-Policy rules might block APIs required by the Faro session manager. When storage is blocked, the collector accepts requests but responds with X-Faro-Session-Status: invalid. The SDK detects this header and attempts to create a new session. If storage is completely blocked (such as Safari or sandboxed iFrames), this renewal fails silently. Data continues to flow to Frontend Observability, but session rotation stops and analytics degrade for long-lived pages.

Considerations for instrumenting cross-origin iFrames

Cross-origin iFrames work only if the browser allows the required APIs. The SDK session manager and several auto-instrumentations require:

  • Access to browser storage
  • Access to the Performance Timeline API
  • Access to other browser APIs subject to the Same-Origin Policy

Any of the following can break instrumentation inside the iFrame:

  • The iFrame domain blocks required APIs through a Content-Security-Policy
  • The iFrame domain doesn’t allow access to the Performance Timeline API
  • The SDK can’t store or read session data
  • Third-party restrictions applied by the site owner

When storage access is blocked, the collector accepts requests but responds with X-Faro-Session-Status: invalid. The SDK detects this header and attempts to create a new session. If storage remains unavailable, renewal fails silently. Data continues to flow to Frontend Observability, but session rotation stops and analytics degrade for long-lived pages.

Correlate parent and iFrame activity in cross-origin setups

You might want to track activity inside an iFrame and correlate it with events in the parent page. If the iFrame runs on a different origin, Faro can’t automatically synchronize sessions.

You have two options:

  • Serve the iFrame content from the same origin as the parent application. This is the simplest and most reliable way to ensure session sharing and full correlation.

  • Synchronize sessions across origins using the browser message bus. Use the postMessage API to pass session IDs between parent and iFrame contexts. This allows correlation even when iFrames come from different origins or are nested.

To implement cross-origin session synchronization:

  1. Use window.postMessage() to send session updates between parent and iFrame
  2. Listen for session change events in each context with window.addEventListener('message', ...)
  3. Update Faro’s session metadata when you receive a session ID from another context
  4. Validate message origins for security (never use '*' in production)

Note

Cross-origin session synchronization requires custom implementation. The Faro team is considering adding a utility package to simplify this workflow. Check the Grafana Faro repository for updates.