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_idper 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_idacross 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
| Component | Storage | Lifespan | Purpose |
|---|---|---|---|
session_id | localStorage + cookie | 30 min (timeout) | Primary session identifier |
tab_id | sessionStorage | Browser tab lifetime | Unique tab identifier |
session_number | localStorage | Permanent (resets yearly) | Return visit counter |
session_started | localStorage | 30 min (timeout) | Session start timestamp |
active_seconds | In-memory + localStorage | Session lifetime | True 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?
- First visit ever – No session ID exists
- Timeout expired – Last session was >30 minutes ago
- Session storage cleared – Manual or browser cleanup
- 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
| Parameter | Type | Description | Example |
|---|---|---|---|
event | string | Event name | 'session_start' |
session_id | string | New session identifier | 'sess_1760034620287_lyiym92mb' |
tab_id | string | Tab identifier | 'tab_1760034620291_xz8kl34mn' |
session_number | number | User’s total session count | 6 |
reason | string | Why session started | 'pageload', 'timeout', 'new_visitor' |
timestamp | string | ISO 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:
- New vs Returning Visitors: Track via
session_number - Session Frequency: Analyze how often users return
- Entry Point Analysis: Where do new sessions begin?
- 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
| Parameter | Type | Description | Example |
|---|---|---|---|
event | string | Event name | 'session_ping' |
session_id | string | Session identifier | 'sess_xxx' |
tab_id | string | Tab identifier | 'tab_xxx' |
ping_number | number | Sequential ping count | 3 |
active_seconds | number | Total active time in session | 287 |
is_idle | boolean | Whether user is currently idle | false |
timestamp | string | ISO 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:
- Long Session Detection: Identify high-engagement sessions
- Session Health Monitoring: Debug timeout issues
- Active Time Tracking: Monitor cumulative engagement
- 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
| Trigger | Reason Parameter | Description |
|---|---|---|
| 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
| Parameter | Type | Description | Example |
|---|---|---|---|
event | string | Event name | 'session_exit' |
session_id | string | Session identifier | 'sess_xxx' |
tab_id | string | Tab identifier | 'tab_xxx' |
reason | string | Why session ended | 'beforeunload', 'idle' |
duration | number | Total session duration (seconds) | 420 |
active_seconds | number | Total active engagement | 287 |
timestamp | string | ISO 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:
- Exit Intent Tracking: Understand why users leave
- Session Duration Analysis: True session length
- Multi-Module Coordination: Trigger all summary events
- 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 pagesession_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
| Parameter | Type | Description | Example |
|---|---|---|---|
event | string | Event name | 'page_exit' |
session_id | string | Session identifier | 'sess_xxx' |
tab_id | string | Tab identifier | 'tab_xxx' |
reason | string | Why page exited | 'hidden' |
timestamp | string | ISO 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:
- Exit Page Analysis: Which pages cause users to leave?
- Navigation Flow: Track page-to-page movement
- 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
| Hook | When It Fires | Use Case |
|---|---|---|
start | Session begins | Initialize module state |
exit | Session ends (old format) | Legacy support |
onExit | Session ends (new format) | Return summary data |
ping | Every heartbeat | Periodic checks |
idle | User goes idle | Pause timers |
active | User returns from idle | Resume 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.
| Key | Value | Purpose |
|---|---|---|
adt_session_id | sess_xxx | Session identifier |
adt_session_started | 1760034620287 | Session start timestamp |
adt_session_number | 6 | Return visitor count |
adt_session_year | 2025 | Current year for reset |
adt_active_seconds_lifetime | 15847 | Cumulative active time |
adt_active_date | 2025-01-09 | Last active date |
adt_active_seconds_today | 287 | Today’s active time |
adt_session_broadcast | {...} | Cross-tab messages |
sessionStorage (Tab-Level)
Persists only within the current tab (cleared on tab close).
| Key | Value | Purpose |
|---|---|---|
adt_tab_id | tab_xxx | Unique 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:
- Premium status:
console.log('Is premium:', window.isADTPremium?.());
// Must return true for session manager to load
- Script loaded:
console.log('Session initialized:', window._adtSessionInitialized);
// Should be true after page load
- Consent (if required):
console.log('Has consent:', window.ADTConsent?.analytics);
// If delay_until_consent enabled, must be true
- 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:
- localStorage working:
localStorage.setItem('test', '123');
console.log(localStorage.getItem('test')); // Should be '123'
- 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
- 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:
- localStorage broadcasts:
console.log('Broadcast key:', localStorage.getItem('adt_session_broadcast'));
// Should contain JSON when tab exits
- Exit flag:
console.log('Exit fired:', window.ADTSession?.exitFired);
// Should be true after first exit
- 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:
- User is interacting:
// Ensure user is actually moving mouse, scrolling, etc.
// Passive tab viewing doesn't count
- Idle timeout:
console.log('Is idle:', window.ADTSession.isIdle());
console.log('Last activity:', new Date(sessionState.lastActivity));
- 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:
- 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();
- 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)
- 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 Tracking – session_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 beginssession_ping– Heartbeat (every 5 min)session_exit– Session terminatespage_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