Session Management Guide

Table of Contents

  • Overview
  • Core Concepts
  • Session Lifecycle
    • Session Start
    • Session Ping (Heartbeat)
    • Session Exit
    • Page Exit
  • Session Identifiers
  • Session Timeout & Idle Detection
  • Cross-Tab Coordination
  • Hook System
  • Storage & Persistence
  • API Reference
  • Implementation Examples
  • Best Practices
  • Troubleshooting

Overview

The Session Management module is a premium feature that provides comprehensive session tracking, cross-page persistence, and multi-tab coordination. It’s the foundation that ties all other tracking events together.

What Is A Session?

A session is a continuous period of user activity on your site, identified by a unique session_id. Sessions:

  • Start when a user lands on your site
  • Continue across multiple page views
  • Timeout after 30 minutes of inactivity (configurable)
  • Track engagement across tabs (with unique tab_id per tab)

Why Session Management Matters

Traditional analytics problems:

  • ❌ Can’t track users across page reloads
  • ❌ No multi-tab session awareness
  • ❌ Lost context when users return
  • ❌ Inconsistent session definitions

ADT Session Management solutions:

  • ✅ Persistent session_id across all pages
  • ✅ Multi-tab tracking with unique tab_id
  • ✅ Return visitor identification with session_number
  • ✅ Coordinated exit tracking across tabs
  • ✅ Hook system for other modules to tap into session lifecycle

Core Concepts

Session Components

ComponentStorageLifespanPurpose
session_idlocalStorage + cookie30 min (timeout)Primary session identifier
tab_idsessionStorageBrowser tab lifetimeUnique tab identifier
session_numberlocalStoragePermanent (resets yearly)Return visit counter
session_startedlocalStorage30 min (timeout)Session start timestamp
active_secondsIn-memory + localStorageSession lifetimeTrue engagement time

Session States

// Normal state
state: {
  sessionId: 'sess_1760034620287_lyiym92mb',
  tabId: 'tab_1760034620291_xz8kl34mn',
  sessionNumber: 6,
  startTime: 1760034620287,
  pingCount: 0,
  exitFired: false,
  activeSeconds: 0,
  lastActivity: 1760034620287,
  isIdle: false
}

Configuration

config: {
  sessionKey: 'adt_session_id',           // localStorage key for session ID
  startedKey: 'adt_session_started',      // localStorage key for start time
  tabKey: 'adt_tab_id',                   // sessionStorage key for tab ID
  numberKey: 'adt_session_number',        // localStorage key for session count
  yearKey: 'adt_session_year',            // localStorage key for year
  timeoutMinutes: 30,                     // Session timeout (default: 30)
  heartbeatMinutes: 5,                    // Ping interval (default: 5)
  pushSessionPings: true,                 // Enable/disable ping events
  pushSessionExit: true,                  // Enable/disable exit events
  pushPageExit: true                      // Enable/disable page_exit events
}

Session Lifecycle

Session Start

Event Name: session_start
Fires When: New session begins
Frequency: Once per new session (not on page reload)

When Does A New Session Start?

  1. First visit ever – No session ID exists
  2. Timeout expired – Last session was >30 minutes ago
  3. Session storage cleared – Manual or browser cleanup
  4. New year – Session number resets annually

Session Start Logic

// Check for existing session
const existingId = localStorage.getItem('adt_session_id');
const startedAt = localStorage.getItem('adt_session_started');
const now = Date.now();
const timeoutMs = 30 * 60 * 1000; // 30 minutes

// Is existing session still valid?
if (existingId && startedAt && (now - startedAt < timeoutMs)) {
  // EXISTING SESSION - Continue with same session_id
  // NO session_start event fires
  sessionId = existingId;
} else {
  // NEW SESSION - Generate new session_id
  sessionId = 'sess_' + Date.now() + '_' + randomString();
  
  // Increment session_number
  sessionNumber++;
  
  // Fire session_start event
  window.dataLayer.push({
    event: 'session_start',
    session_id: sessionId,
    tab_id: tabId,
    session_number: sessionNumber,
    reason: 'pageload', // or 'timeout', 'new_visitor'
    timestamp: new Date().toISOString()
  });
}

Event Parameters

ParameterTypeDescriptionExample
eventstringEvent name'session_start'
session_idstringNew session identifier'sess_1760034620287_lyiym92mb'
tab_idstringTab identifier'tab_1760034620291_xz8kl34mn'
session_numbernumberUser’s total session count6
reasonstringWhy session started'pageload', 'timeout', 'new_visitor'
timestampstringISO timestamp'2025-01-09T14:30:00.000Z'

DataLayer Push

// Single push (no two-push pattern for session events)
window.dataLayer.push({
  event: 'session_start',
  session_id: 'sess_1760034620287_lyiym92mb',
  tab_id: 'tab_1760034620291_xz8kl34mn',
  session_number: 6,
  reason: 'pageload',
  timestamp: '2025-01-09T14:30:00.000Z'
});

Important: Not Every Page Load Fires session_start

// Scenario 1: First page load (NEW session)
User lands on homepage
→ session_start fires
→ session_id: sess_abc123

// Scenario 2: Navigate to another page (SAME session)
User clicks to /about page (5 seconds later)
→ NO session_start (still same session)
→ session_id: sess_abc123 (unchanged)

// Scenario 3: Return after 10 minutes (SAME session)
User returns to site after 10 minutes
→ NO session_start (still within 30 min timeout)
→ session_id: sess_abc123 (unchanged)

// Scenario 4: Return after 45 minutes (NEW session)
User returns to site after 45 minutes (> timeout)
→ session_start fires
→ session_id: sess_xyz789 (new ID)
→ session_number: 7 (incremented)

Business Value

Use Cases:

  1. New vs Returning Visitors: Track via session_number
  2. Session Frequency: Analyze how often users return
  3. Entry Point Analysis: Where do new sessions begin?
  4. Time-Based Segmentation: Sessions by time of day

Session Ping (Heartbeat)

Event Name: session_ping
Fires When: Every 5 minutes during active session
Frequency: Multiple times per session

Description

A “heartbeat” event that fires periodically to indicate the session is still active. This helps:

  • Track long sessions that span multiple pages
  • Identify if user is still engaged
  • Provide session health monitoring
  • Debug session timeout issues

Firing Pattern

Session starts → Wait 5 minutes → Ping #1 fires
Wait 5 minutes → Ping #2 fires
Wait 5 minutes → Ping #3 fires
...continues until session exit

Event Parameters

ParameterTypeDescriptionExample
eventstringEvent name'session_ping'
session_idstringSession identifier'sess_xxx'
tab_idstringTab identifier'tab_xxx'
ping_numbernumberSequential ping count3
active_secondsnumberTotal active time in session287
is_idlebooleanWhether user is currently idlefalse
timestampstringISO timestamp'2025-01-09T14:30:00.000Z'

DataLayer Push

window.dataLayer.push({
  event: 'session_ping',
  session_id: 'sess_1760034620287_lyiym92mb',
  tab_id: 'tab_1760034620291_xz8kl34mn',
  ping_number: 3,
  active_seconds: 287,
  is_idle: false,
  timestamp: '2025-01-09T14:30:00.000Z'
});

Configuration

// Disable pings if not needed (reduces event volume)
SessionManager.config.pushSessionPings = false;

// Change ping interval (default: 5 minutes)
SessionManager.config.heartbeatMinutes = 10; // Ping every 10 minutes

Business Value

Use Cases:

  1. Long Session Detection: Identify high-engagement sessions
  2. Session Health Monitoring: Debug timeout issues
  3. Active Time Tracking: Monitor cumulative engagement
  4. Idle Detection: Identify when users walk away

Example Analysis:

-- Sessions with most pings (longest engaged sessions)
SELECT 
  session_id,
  MAX(ping_number) as max_pings,
  MAX(ping_number) * 5 as estimated_duration_minutes,
  MAX(active_seconds) as total_active_seconds
FROM session_ping_events
GROUP BY session_id
ORDER BY max_pings DESC
LIMIT 100

Session Exit

Event Name: session_exit
Fires When: Session terminates
Frequency: Once per session

Description

Fires when a session ends, triggering all registered exit hooks to push their summary events. This is the master orchestrator for all session_*_summary events.

Exit Triggers

TriggerReason ParameterDescription
User closes tab'beforeunload'Browser beforeunload event
Idle timeout'idle'No activity for 30 minutes
Manual trigger'manual'Developer-triggered exit
Tab hidden'hidden'Tab visibility change (if configured)

How Session Exit Works

// Step 1: User triggers exit (e.g., closes tab)
window.addEventListener('beforeunload', () => {
  SessionManager.exitSession('beforeunload');
});

// Step 2: exitSession() collects data from all registered hooks
exitSession(reason) {
  // Collect data from onExit hooks
  const hookResults = {
    engagement: engagementModule.getExitData(),
    content: contentModule.getExitData(),
    attribution: attributionModule.getExitData(),
    // ... etc
  };
  
  // Step 3: Push individual summary events
  if (hookResults.engagement) {
    window.dataLayer.push({
      event: 'session_engagement_summary',
      ...hookResults.engagement
    });
  }
  
  if (hookResults.content) {
    window.dataLayer.push({
      event: 'session_content_summary',
      ...hookResults.content
    });
  }
  
  // ... and so on for all modules
}

Exit Hook Registration

Other modules register hooks to provide their data on exit:

// Engagement module registers its hook
window.ADTSession.registerHook('onExit', 'engagement', function(reason) {
  // Return summary data
  return {
    session_id: window.ADTSession.id(),
    exit_reason: reason,
    final_engagement_metrics: {
      time_on_page: window._adtTimeOnPage,
      active_time: window._adtActiveTime,
      scroll_depth: Math.max(...window._adtScrollDepths),
      // ... etc
    },
    page_quality_score: calculateQualityScore()
  };
});

// When session exits, this hook is called automatically
// and session_engagement_summary event fires with the returned data

Event Parameters

ParameterTypeDescriptionExample
eventstringEvent name'session_exit'
session_idstringSession identifier'sess_xxx'
tab_idstringTab identifier'tab_xxx'
reasonstringWhy session ended'beforeunload', 'idle'
durationnumberTotal session duration (seconds)420
active_secondsnumberTotal active engagement287
timestampstringISO timestamp'2025-01-09T14:30:00.000Z'

DataLayer Push

window.dataLayer.push({
  event: 'session_exit',
  session_id: 'sess_1760034620287_lyiym92mb',
  tab_id: 'tab_1760034620291_xz8kl34mn',
  reason: 'beforeunload',
  duration: 420,
  active_seconds: 287,
  timestamp: '2025-01-09T14:30:00.000Z'
});

Configuration

// Disable session_exit event (but summaries still fire)
SessionManager.config.pushSessionExit = false;

Cross-Tab Exit Coordination

When one tab exits, it broadcasts to other tabs:

// Tab 1 closes
exitSession('beforeunload');
→ Broadcasts 'session_exit' via localStorage

// Tab 2 receives broadcast
→ Marks session as exited
→ Stops heartbeat
→ Prevents duplicate exits

Business Value

Use Cases:

  1. Exit Intent Tracking: Understand why users leave
  2. Session Duration Analysis: True session length
  3. Multi-Module Coordination: Trigger all summary events
  4. Cross-Tab Deduplication: Ensure single exit per session

Page Exit

Event Name: page_exit
Fires When: User navigates away or tab becomes hidden
Frequency: Once per page

Description

Fires when user leaves the current page (but session may continue). Different from session_exit:

  • page_exit = User leaves THIS page
  • session_exit = User’s entire session ends

Firing Scenarios

// Scenario 1: Navigate to another page
User clicks link from /home to /about
→ page_exit fires on /home (reason: 'hidden')
→ Session continues with same session_id

// Scenario 2: Close tab
User closes tab on /pricing
→ page_exit fires (reason: 'hidden')
→ session_exit fires immediately after
→ Session ends

// Scenario 3: Switch tabs
User switches from your tab to Gmail
→ page_exit fires (reason: 'hidden')
→ Session continues (user may return)

Event Parameters

ParameterTypeDescriptionExample
eventstringEvent name'page_exit'
session_idstringSession identifier'sess_xxx'
tab_idstringTab identifier'tab_xxx'
reasonstringWhy page exited'hidden'
timestampstringISO timestamp'2025-01-09T14:30:00.000Z'

DataLayer Push

window.dataLayer.push({
  event: 'page_exit',
  session_id: 'sess_1760034620287_lyiym92mb',
  tab_id: 'tab_1760034620291_xz8kl34mn',
  reason: 'hidden',
  timestamp: '2025-01-09T14:30:00.000Z'
});

Configuration

// Disable page_exit events
SessionManager.config.pushPageExit = false;

Business Value

Use Cases:

  1. Exit Page Analysis: Which pages cause users to leave?
  2. Navigation Flow: Track page-to-page movement
  3. Tab Switching Behavior: Identify multi-tasking patterns

Session Identifiers

session_id

Format: sess_[timestamp]_[random]
Example: sess_1760034620287_lyiym92mb
Storage: localStorage + cookie
Lifetime: 30 minutes of inactivity

Generation

function generateSessionId() {
  const timestamp = Date.now();
  const random = Math.random().toString(36).substr(2, 9);
  return `sess_${timestamp}_${random}`;
}

// Example outputs:
// sess_1760034620287_lyiym92mb
// sess_1760034650123_xz8kl34mn
// sess_1760034680456_pq3rs78wx

Persistence

// Stored in multiple places for reliability
localStorage.setItem('adt_session_id', sessionId);
localStorage.setItem('adt_session_started', Date.now());

// Also set cookie for cross-domain (if needed)
document.cookie = `adt_session_id=${sessionId}; path=/; max-age=1800; SameSite=Lax`;

Access

// From anywhere in code
const sessionId = window.ADTSession.id();
console.log(sessionId); // 'sess_1760034620287_lyiym92mb'

tab_id

Format: tab_[timestamp]_[random]
Example: tab_1760034620291_xz8kl34mn
Storage: sessionStorage (tab-specific)
Lifetime: Browser tab lifetime

Purpose

Distinguishes between multiple tabs in the same session:

// User opens Tab 1
session_id: sess_abc123
tab_id: tab_xyz789

// User opens Tab 2 (same session)
session_id: sess_abc123  // ← Same session_id
tab_id: tab_def456       // ← Different tab_id

// This enables multi-tab analysis:
// - Which tab converted?
// - How many tabs did user open?
// - Which tab was most engaged?

Access

const tabId = window.ADTSession.tabId();
console.log(tabId); // 'tab_1760034620291_xz8kl34mn'

session_number

Format: Integer (increments)
Example: 6 (user’s 6th session)
Storage: localStorage
Lifetime: Permanent (resets annually)

Purpose

Tracks return visits:

// First visit
session_number: 1

// Second visit (returns later)
session_number: 2

// Third visit
session_number: 3

// ... and so on

// Resets on January 1st each year

Year Reset Logic

const currentYear = new Date().getFullYear();
const storedYear = parseInt(localStorage.getItem('adt_session_year'));

if (storedYear !== currentYear) {
  // New year - reset counter
  sessionNumber = 1;
  localStorage.setItem('adt_session_year', currentYear);
} else {
  // Same year - increment
  sessionNumber++;
}

Business Use Cases

// New vs Returning Visitors
if (session_number === 1) {
  // First-time visitor
  showOnboardingFlow();
} else if (session_number >= 5) {
  // Highly engaged returning user
  showLoyaltyOffer();
}

// Segmentation
const segment = session_number === 1 ? 'new' :
                session_number <= 3 ? 'exploring' :
                session_number <= 10 ? 'engaged' :
                'loyal';

Access

const sessionNumber = window.ADTSession.number();
console.log(sessionNumber); // 6

Session Timeout & Idle Detection

Session Timeout

Default: 30 minutes of inactivity
Configurable: Via window.ADTData.session_timeout_minutes

How It Works

// Every user interaction resets the timeout
['click', 'scroll', 'keydown', 'mousemove', 'touchstart'].forEach(event => {
  document.addEventListener(event, () => {
    resetIdleTimer();
  });
});

// Idle timer
let idleTimer;
function resetIdleTimer() {
  clearTimeout(idleTimer);
  
  // Start 30-minute countdown
  idleTimer = setTimeout(() => {
    // No activity for 30 minutes → Session ends
    handleIdle();
  }, 30 * 60 * 1000);
}

function handleIdle() {
  state.isIdle = true;
  executeHooks('idle', { sessionId: state.sessionId });
  exitSession('idle'); // End session
}

Activity Detection

The following interactions reset the idle timer:

  • ✅ Mouse movement
  • ✅ Scrolling
  • ✅ Keyboard input
  • ✅ Clicks/taps
  • ✅ Touch interactions

Passive states (timer keeps running):

  • ❌ Tab in background
  • ❌ Browser minimized
  • ❌ No user interaction

Idle State

Property: state.isIdle
Values: true or false

// Check if user is idle
const isIdle = window.ADTSession.isIdle();

if (isIdle) {
  // User has been inactive for 30 minutes
  // Session is about to end
}

Idle → Active Transition

// User was idle, then returns
if (wasIdle && userInteracts) {
  state.isIdle = false;
  
  // Execute 'active' hooks
  executeHooks('active', {
    sessionId: state.sessionId
  });
}

Business Use Cases

// Show re-engagement message
if (state.isIdle && userReturns) {
  showWelcomeBackMessage();
}

// Pause timers when idle
if (state.isIdle) {
  pauseVideoPlaybackTimer();
}

Active Time Tracking

Property: state.activeSeconds
Update Frequency: Every 1 second
Storage: In-memory + localStorage

Tracking Logic

// Every 1 second
setInterval(() => {
  const now = Date.now();
  const lastActivity = state.lastActivity;
  
  // Only increment if user was active in last 30 seconds
  if (!state.isIdle && (now - lastActivity < 30000)) {
    state.activeSeconds++;
    
    // Also track lifetime and daily active time
    incrementLifetimeActiveTime();
    incrementDailyActiveTime();
  }
}, 1000);

Three Levels of Active Time

// 1. Session Active Time (in-memory)
const sessionActive = window.ADTSession.getActiveTime();
// Resets on new session

// 2. Daily Active Time (localStorage)
const dailyActive = localStorage.getItem('adt_active_seconds_today');
// Resets daily

// 3. Lifetime Active Time (localStorage)
const lifetimeActive = localStorage.getItem('adt_active_seconds_lifetime');
// Never resets (cumulative)

Access

const activeSeconds = window.ADTSession.getActiveTime();
console.log('User has been actively engaged for:', activeSeconds, 'seconds');

Cross-Tab Coordination

The Multi-Tab Problem

// User opens 3 tabs:
// Tab 1: Homepage
// Tab 2: Pricing page
// Tab 3: About page

// All tabs have the SAME session_id
// But each has a unique tab_id

// When user closes Tab 1:
// - Only Tab 1 should fire session_exit
// - Tabs 2 and 3 should NOT fire duplicate exits

How ADT Solves This

1. Tab-Specific sessionStorage

// Each tab stores its own tab_id in sessionStorage
// sessionStorage is tab-specific (not shared)
sessionStorage.setItem('adt_tab_id', 'tab_xyz789');

// Tab 1: tab_xyz789
// Tab 2: tab_abc123
// Tab 3: tab_def456

2. Broadcast System

// When Tab 1 closes, broadcast to other tabs via localStorage
exitSession(reason) {
  // Mark as exited locally
  state.exitFired = true;
  
  // Broadcast to other tabs
  broadcast('session_exit', { reason });
  
  // Push summary events
  pushSummaryEvents();
}

function broadcast(event, data) {
  const message = {
    event: event,
    sessionId: state.sessionId,
    data: data,
    timestamp: Date.now()
  };
  
  // Write to localStorage (triggers 'storage' event in other tabs)
  localStorage.setItem('adt_session_broadcast', JSON.stringify(message));
}

3. Receive Broadcasts

// Tab 2 and Tab 3 listen for localStorage changes
window.addEventListener('storage', (e) => {
  if (e.key === 'adt_session_broadcast' && e.newValue) {
    const message = JSON.parse(e.newValue);
    
    if (message.event === 'session_exit') {
      // Another tab already exited this session
      state.exitFired = true;
      stopHeartbeat();
      
      // Do NOT fire duplicate exit
    }
  }
});

Result: Single Exit Per Session

// Tab 1 closes
→ Fires session_exit ✅
→ Broadcasts to other tabs
→ Pushes all summary events ✅

// Tab 2 receives broadcast
→ Marks session as exited
→ Does NOT fire duplicate exit ❌

// Tab 3 receives broadcast
→ Marks session as exited
→ Does NOT fire duplicate exit ❌

// Result: Clean, single exit event per session

Hook System

The hook system allows other modules to tap into session lifecycle events.

Available Hooks

HookWhen It FiresUse Case
startSession beginsInitialize module state
exitSession ends (old format)Legacy support
onExitSession ends (new format)Return summary data
pingEvery heartbeatPeriodic checks
idleUser goes idlePause timers
activeUser returns from idleResume activity

Hook Registration

Old Format (2-parameter)

// Legacy format - still supported
window.ADTSession.registerHook('exit', function(data) {
  console.log('Session exited:', data.sessionId);
});

New Format (3-parameter) – RECOMMENDED

// New format for onExit hooks
window.ADTSession.registerHook('onExit', 'moduleName', function(reason) {
  // Return data object for summary event
  return {
    session_id: window.ADTSession.id(),
    module_specific_data: getModuleData(),
    exit_reason: reason
  };
});

Example: Engagement Module Hook

// From adt-engagement-tracking.js
window.ADTSession.registerHook('onExit', 'engagement', function(reason) {
  const scrollDepths = Array.from(window._adtScrollDepths || []);
  const maxScroll = scrollDepths.length > 0 ? Math.max(...scrollDepths) : 0;
  
  return {
    session_id: window.ADTSession.id(),
    exit_reason: reason,
    final_engagement_metrics: {
      time_on_page: window._adtTimeOnPage || 0,
      active_time: window._adtActiveTime || 0,
      scroll_depth: maxScroll,
      interactions: window._adtInteractionCount || 0,
      clicks: window._adtClickCount || 0,
      form_starts: window._adtFormStarts || 0,
      tab_switches: window._adtTabSwitches || 0,
      scroll_backs: window._adtScrollBacks || 0
    },
    page_quality_score: calculateQualityScore(),
    timestamp: new Date().toISOString()
  };
});

// When session exits, this hook is called and returns data
// Session manager then pushes:
window.dataLayer.push({
  event: 'session_engagement_summary',
  ...hookReturnValue
});

Hook Execution Order

exitSession(reason) {
  // 1. Execute legacy 'exit' hooks
  executeHooks('exit', { sessionId, reason });
  
  // 2. Collect data from 'onExit' hooks
  const hookResults = {};
  Object.keys(hooks.onExit).forEach(name => {
    hookResults[name] = hooks.onExit[name](reason);
  });
  
  // 3. Push individual summary events
  if (hookResults.engagement) {
    window.dataLayer.push({
      event: 'session_engagement_summary',
      ...hookResults.engagement
    });
  }
  
  if (hookResults.attribution) {
    window.dataLayer.push({
      event: 'session_attribution_summary',
      ...hookResults.attribution
    });
  }
  
  // ... etc for all registered modules
}

Creating Your Own Hook

// Example: Custom module tracking user preferences
(function() {
  'use strict';
  
  let userPreferences = {
    darkMode: false,
    fontSize: 'medium',
    preferredCategory: null
  };
  
  // Register exit hook
  if (window.ADTSession && !window.ADTSession._isStub) {
    window.ADTSession.registerHook('onExit', 'preferences', function(reason) {
      return {
        session_id: window.ADTSession.id(),
        exit_reason: reason,
        user_preferences: userPreferences,
        timestamp: new Date().toISOString()
      };
    });
    
    console.log('[Preferences] Exit hook registered');
  }
  
  // Track preference changes
  window.trackPreferenceChange = function(key, value) {
    userPreferences[key] = value;
  };
})();

// Usage:
// User toggles dark mode
trackPreferenceChange('darkMode', true);

// On session exit, 'session_preferences_summary' event fires with:
// {
//   event: 'session_preferences_summary',
//   session_id: 'sess_xxx',
//   user_preferences: {
//     darkMode: true,
//     fontSize: 'medium',
//     preferredCategory: 'electronics'
//   }
// }

Storage & Persistence

localStorage (Session-Level)

Persists across tabs and browser restarts until manually cleared.

KeyValuePurpose
adt_session_idsess_xxxSession identifier
adt_session_started1760034620287Session start timestamp
adt_session_number6Return visitor count
adt_session_year2025Current year for reset
adt_active_seconds_lifetime15847Cumulative active time
adt_active_date2025-01-09Last active date
adt_active_seconds_today287Today’s active time
adt_session_broadcast{...}Cross-tab messages

sessionStorage (Tab-Level)

Persists only within the current tab (cleared on tab close).

KeyValuePurpose
adt_tab_idtab_xxxUnique tab identifier

Cookies (Optional)

// Set for cross-domain tracking (if needed)
document.cookie = `adt_session_id=${sessionId}; path=/; max-age=1800; SameSite=Lax`;

// Cookie properties:
// - path=/          → Available on all pages
// - max-age=1800    → 30 minutes (matches timeout)
// - SameSite=Lax    → Basic CSRF protection

Storage Quota Handling

// Safe storage with error handling
storage: {
  set(key, value) {
    try {
      localStorage.setItem(key, String(value));
      return true;
    } catch (e) {
      // Storage quota exceeded
      console.warn('[ADT] Storage quota exceeded, session data not persisted');
      return false;
    }
  },
  
  get(key, defaultValue = null) {
    try {
      return localStorage.getItem(key) || defaultValue;
    } catch (e) {
      return defaultValue;
    }
  }
}

API Reference

window.ADTSession

The global API object for session management.

Methods

.id()

Returns current session ID.

const sessionId = window.ADTSession.id();
// Returns: 'sess_1760034620287_lyiym92mb' or null
.tabId()

Returns current tab ID.

const tabId = window.ADTSession.tabId();
// Returns: 'tab_1760034620291_xz8kl34mn' or null
.number()

Returns session number (return visit count).

const sessionNumber = window.ADTSession.number();
// Returns: 6 (or 0 if no session)
.getActiveTime()

Returns cumulative active seconds in current session.

const activeSeconds = window.ADTSession.getActiveTime();
// Returns: 287
.isIdle()

Returns whether user is currently idle.

const isIdle = window.ADTSession.isIdle();
// Returns: true or false
.registerHook(event, nameOrCallback, callback)

Registers a hook for session lifecycle events.

3-parameter format (recommended):

window.ADTSession.registerHook('onExit', 'myModule', function(reason) {
  return {
    // Return data for summary event
  };
});

2-parameter format (legacy):

window.ADTSession.registerHook('exit', function(data) {
  // Execute code on exit
});
.getContext()

Returns full session context object.

const context = window.ADTSession.getContext();
// Returns:
// {
//   session_id: 'sess_xxx',
//   tab_id: 'tab_xxx',
//   session_number: 6,
//   active_seconds: 287
// }
.triggerExit(reason)

Manually trigger session exit (for testing or special cases).

window.ADTSession.triggerExit('manual');
// Forces session exit immediately

Checking If Session Manager Is Available

// Check if session manager is loaded and active
if (window.ADTSession && !window.ADTSession._isStub) {
  // Real session manager is active
  const sessionId = window.ADTSession.id();
} else {
  // Session manager is a stub (not premium or not loaded)
  console.log('Session manager not available');
}

Implementation Examples

Example 1: Add Session Context To Custom Events

// Add session context to any custom event
function trackCustomEvent(eventName, eventData) {
  const payload = {
    event: eventName,
    ...eventData
  };
  
  // Add session context if available
  if (window.ADTSession && !window.ADTSession._isStub) {
    payload.session_id = window.ADTSession.id();
    payload.tab_id = window.ADTSession.tabId();
    payload.session_number = window.ADTSession.number();
  }
  
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push(payload);
}

// Usage:
trackCustomEvent('button_click', {
  button_id: 'cta_primary',
  button_text: 'Start Free Trial'
});

// Pushes:
// {
//   event: 'button_click',
//   button_id: 'cta_primary',
//   button_text: 'Start Free Trial',
//   session_id: 'sess_xxx',
//   tab_id: 'tab_xxx',
//   session_number: 6
// }

Example 2: Session-Based Personalization

// Show different content based on session number
(function() {
  const sessionNumber = window.ADTSession?.number() || 0;
  
  if (sessionNumber === 1) {
    // First-time visitor
    document.getElementById('hero-message').textContent = 
      'Welcome! New here? Let us show you around.';
    showOnboardingTooltips();
    
  } else if (sessionNumber >= 2 && sessionNumber <= 4) {
    // Returning visitor (exploring)
    document.getElementById('hero-message').textContent = 
      'Welcome back! Pick up where you left off.';
    showRecentActivity();
    
  } else if (sessionNumber >= 5) {
    // Loyal user
    document.getElementById('hero-message').textContent = 
      'Hey there, friend! Check out what\'s new.';
    showLoyaltyPerks();
  }
})();

Example 3: Engagement-Based Exit Intent

// Show exit offer only to engaged users
(function() {
  let highEngagement = false;
  
  // Track when user becomes highly engaged
  if (window.ADTSession && !window.ADTSession._isStub) {
    window.ADTSession.registerHook('ping', function(data) {
      const activeSeconds = window.ADTSession.getActiveTime();
      
      if (activeSeconds >= 120) { // 2+ minutes active
        highEngagement = true;
      }
    });
  }
  
  // Show exit intent modal only for engaged users
  document.addEventListener('mouseleave', function(e) {
    if (e.clientY <= 0 && highEngagement) {
      showExitIntentModal({
        title: 'Wait! Before you go...',
        offer: '20% off your first order',
        reason: 'high_engagement'
      });
    }
  });
})();

Example 4: Multi-Tab Session Analysis

// Track which tab converted
(function() {
  if (!window.ADTSession || window.ADTSession._isStub) return;
  
  // On conversion
  document.getElementById('checkout-form').addEventListener('submit', function(e) {
    const tabId = window.ADTSession.tabId();
    const sessionId = window.ADTSession.id();
    
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'conversion',
      session_id: sessionId,
      tab_id: tabId, // Which tab converted
      form_id: 'checkout-form',
      conversion_type: 'purchase',
      timestamp: new Date().toISOString()
    });
    
    // Later in analytics:
    // - How many tabs were open before conversion?
    // - Did user open pricing page in multiple tabs?
    // - Which tab drove the conversion?
  });
})();

Example 5: Session Summary Testing

// Manually trigger session exit for testing
function testSessionSummaries() {
  console.log('=== Testing Session Summaries ===');
  
  if (!window.ADTSession || window.ADTSession._isStub) {
    console.error('Session manager not available');
    return;
  }
  
  // Check current state
  console.log('Current session:', {
    session_id: window.ADTSession.id(),
    tab_id: window.ADTSession.tabId(),
    session_number: window.ADTSession.number(),
    active_seconds: window.ADTSession.getActiveTime(),
    is_idle: window.ADTSession.isIdle()
  });
  
  // Listen for summary events
  const originalPush = window.dataLayer.push;
  const caughtEvents = [];
  
  window.dataLayer.push = function(...args) {
    if (args[0]?.event?.includes('summary')) {
      console.log('📤 Summary event:', args[0].event);
      caughtEvents.push(args[0]);
    }
    return originalPush.apply(this, args);
  };
  
  // Trigger exit
  console.log('🔥 Triggering session exit...');
  window.ADTSession.triggerExit('manual_test');
  
  // Restore original push
  setTimeout(() => {
    window.dataLayer.push = originalPush;
    console.log(`✅ Test complete! Caught ${caughtEvents.length} summary events:`);
    caughtEvents.forEach(e => console.log(`  - ${e.event}`));
  }, 100);
}

// Run test
testSessionSummaries();

Best Practices

1. Always Check For Session Manager Availability

// ✅ Good
if (window.ADTSession && !window.ADTSession._isStub) {
  const sessionId = window.ADTSession.id();
  // Use session ID
}

// ❌ Bad (will error if not loaded)
const sessionId = window.ADTSession.id();

2. Use Session Context In All Custom Events

// ✅ Good - Include session context
window.dataLayer.push({
  event: 'custom_event',
  custom_data: 'value',
  session_id: window.ADTSession?.id() || null,
  session_number: window.ADTSession?.number() || 0,
  tab_id: window.ADTSession?.tabId() || null
});

// ❌ Bad - Missing session context
window.dataLayer.push({
  event: 'custom_event',
  custom_data: 'value'
});

3. Register Hooks Early

// ✅ Good - Register as soon as possible
(function() {
  if (window.ADTSession && !window.ADTSession._isStub) {
    window.ADTSession.registerHook('onExit', 'myModule', function(reason) {
      return getMyModuleData();
    });
  }
})();

// ❌ Bad - Registering too late (after user might exit)
window.addEventListener('load', function() {
  // User might have already left by now
  window.ADTSession.registerHook('onExit', 'myModule', function(reason) {
    return getMyModuleData();
  });
});

4. Use Descriptive Hook Names

// ✅ Good
window.ADTSession.registerHook('onExit', 'videoTracking', function(reason) {
  return getVideoData();
});

// ❌ Bad (generic name)
window.ADTSession.registerHook('onExit', 'module1', function(reason) {
  return getData();
});

5. Handle Storage Failures Gracefully

// ✅ Good
function saveToStorage(key, value) {
  try {
    localStorage.setItem(key, value);
    return true;
  } catch (e) {
    console.warn('Storage failed, using in-memory fallback');
    inMemoryStorage[key] = value;
    return false;
  }
}

// ❌ Bad (unhandled error)
function saveToStorage(key, value) {
  localStorage.setItem(key, value); // Can throw error
}

6. Don’t Block On Session Manager

// ✅ Good - Degrade gracefully
const sessionId = window.ADTSession?.id() || null;
if (sessionId) {
  // Include session context
} else {
  // Continue without session context
}

// ❌ Bad - Waiting indefinitely
while (!window.ADTSession) {
  // Never do this - will freeze browser
}

7. Use Consistent Session Timeout

// ✅ Good - Use same timeout everywhere
const SESSION_TIMEOUT = 30; // minutes

// In PHP settings
'session_timeout_minutes' => 30

// In JavaScript
window.ADTData.session_timeout_minutes = 30;

// ❌ Bad - Inconsistent timeouts
// PHP: 30 minutes
// JS: 20 minutes
// Result: Sessions end unexpectedly

8. Test Multi-Tab Scenarios

// ✅ Good - Test multi-tab behavior
// 1. Open Tab 1
// 2. Open Tab 2 (same session)
// 3. Close Tab 1
// 4. Verify: Only one session_exit fired
// 5. Close Tab 2
// 6. Verify: No duplicate exit

// Document expected behavior:
// - Same session_id across tabs
// - Different tab_id per tab
// - Single exit event per session

Troubleshooting

Session Manager Not Loading

Problem: window.ADTSession is undefined or stub

Check:

  1. Premium status:
console.log('Is premium:', window.isADTPremium?.());
// Must return true for session manager to load
  1. Script loaded:
console.log('Session initialized:', window._adtSessionInitialized);
// Should be true after page load
  1. Consent (if required):
console.log('Has consent:', window.ADTConsent?.analytics);
// If delay_until_consent enabled, must be true
  1. Console errors:
// Check browser console for errors
// Look for: [ADT Session] messages

Solution:

// Enable debug mode to see what's happening
window.ADTData.debug_mode = '1';
// Reload page and check console

Session ID Not Persisting Across Pages

Problem: New session_id on every page load

Check:

  1. localStorage working:
localStorage.setItem('test', '123');
console.log(localStorage.getItem('test')); // Should be '123'
  1. Session timeout:
const started = parseInt(localStorage.getItem('adt_session_started'));
const now = Date.now();
const elapsed = (now - started) / 1000 / 60; // minutes
console.log('Minutes since session start:', elapsed);
// If > 30, session timed out
  1. Storage being cleared:
// Check if something is clearing localStorage
window.addEventListener('storage', (e) => {
  if (e.key === 'adt_session_id' && e.newValue === null) {
    console.error('Session ID was cleared!', e);
  }
});

Multiple session_exit Events

Problem: Same session fires multiple exits

Cause: Cross-tab coordination not working

Check:

  1. localStorage broadcasts:
console.log('Broadcast key:', localStorage.getItem('adt_session_broadcast'));
// Should contain JSON when tab exits
  1. Exit flag:
console.log('Exit fired:', window.ADTSession?.exitFired);
// Should be true after first exit
  1. Multiple tabs:
// Open 2 tabs with same session_id
// Close Tab 1 → Check Tab 2 console
// Should see: "Session exit broadcast received"

Solution:

// Ensure only one tab exits per session
// Check browser console for broadcast messages

session_start Not Firing

Problem: No session_start event in dataLayer

Cause: Session already exists (continuing session)

This is normal!

// First visit (or after timeout)
→ session_start fires ✅

// Navigate to another page (5 seconds later)
→ NO session_start (same session continues) ✅

// Return after 10 minutes
→ NO session_start (still within 30 min timeout) ✅

// Return after 45 minutes
→ session_start fires (new session) ✅

To test new session:

// Clear session storage
localStorage.removeItem('adt_session_id');
localStorage.removeItem('adt_session_started');

// Reload page
location.reload();

// Now session_start should fire

session_ping Flooding dataLayer

Problem: Too many ping events

Solution 1: Disable pings (recommended if not needed)

// In adt-session-manager.js or via config
SessionManager.config.pushSessionPings = false;

// Pings will still execute hooks but won't push to dataLayer

Solution 2: Increase interval

// Change from 5 minutes to 10 minutes
SessionManager.config.heartbeatMinutes = 10;

Solution 3: Filter in GTM

// In GTM, block session_ping from certain tags
// Don't send to GA4 unless needed for specific analysis

Active Time Not Incrementing

Problem: active_seconds stays at 0

Check:

  1. User is interacting:
// Ensure user is actually moving mouse, scrolling, etc.
// Passive tab viewing doesn't count
  1. Idle timeout:
console.log('Is idle:', window.ADTSession.isIdle());
console.log('Last activity:', new Date(sessionState.lastActivity));
  1. Timer running:
// Check if 1-second interval is running
console.log('Active seconds:', window.ADTSession.getActiveTime());
// Should increment every second during activity

Manual test:

// Move mouse continuously for 10 seconds
// Then check:
console.log('Active time:', window.ADTSession.getActiveTime());
// Should be ~10

Session Number Not Incrementing

Problem: Always shows session_number: 1

Check:

  1. Session is actually new:
// Clear session to force new session
localStorage.removeItem('adt_session_id');
localStorage.removeItem('adt_session_started');
localStorage.removeItem('adt_session_number');

// Reload and check
location.reload();
  1. Year reset:
const storedYear = localStorage.getItem('adt_session_year');
const currentYear = new Date().getFullYear();
console.log('Stored year:', storedYear, 'Current year:', currentYear);

// If different, counter resets to 1 (this is correct behavior)
  1. Counter storage:
console.log('Session number:', localStorage.getItem('adt_session_number'));
// Should increment each new session

Summary

Session Management in Advanced DataLayer Tracker:

Persistent Sessions – Tracks users across pages with session_id
Multi-Tab Aware – Unique tab_id per tab, coordinated exits
Return Visitor Trackingsession_number counts visits
Idle Detection – 30-minute timeout with activity tracking
Active Time Tracking – True engagement measurement
Hook System – Modules can tap into session lifecycle
Cross-Page Persistence – Metrics survive navigation
Exit Coordination – Single exit event per session across all tabs

Key Takeaway: Session management is the backbone of ADT. It provides consistent identifiers and lifecycle hooks that enable all other tracking modules to build comprehensive, cross-page user journeys.

Quick Reference

Check Session Status

window.ADTSession.id();          // Current session ID
window.ADTSession.tabId();       // Current tab ID
window.ADTSession.number();      // Session count
window.ADTSession.getActiveTime(); // Active seconds
window.ADTSession.isIdle();      // Idle state
window.ADTSession.getContext();  // Full context object

Events Fired

  • session_start – New session begins
  • session_ping – Heartbeat (every 5 min)
  • session_exit – Session terminates
  • page_exit – Page navigation/hide

Storage Locations

  • localStorage: session_id, session_number, active time
  • sessionStorage: tab_id
  • cookie: session_id (optional, cross-domain)

Debug

window.ADTData.debug_mode = '1';
console.log(window.ADTSession.getContext());

This documentation covers all session management features in Advanced DataLayer Tracker

Was this article helpful?