Table of Contents
- Overview
- Core Engagement Events
- scroll_depth
- scroll_back_up
- time_on_page
- active_time (MOST IMPORTANT)
- focus_blur (Tab Visibility)
- hover_intent
- Session Engagement Milestone
- Session Engagement Summary
- Engagement Metrics Object
- Page Quality Score
- Implementation Examples
- Best Practices
- Troubleshooting
Overview
Engagement tracking measures how users actually interact with your content, going far beyond simple pageviews. These events reveal true user intent, content effectiveness, and session quality.
Why Engagement Tracking Matters
Traditional analytics tells you:
- User visited the page
- User spent 2 minutes (maybe)
ADT Engagement tracking tells you:
- User actively engaged for 1 minute 47 seconds (not just having tab open)
- User scrolled to 75% depth and scrolled back up to re-read pricing
- User switched tabs 3 times (comparison shopping)
- User hovered over the CTA for 2 seconds but didn’t click (hesitation)
- Page Quality Score: 87/100 (high-intent session)
This granular data enables:
- Real-time personalization (show offers to engaged users)
- Quality-based attribution (weight conversions by engagement)
- Content optimization (find where users lose interest)
- Audience segmentation (target high-quality sessions)
Learn More: Session Management Guide
Core Engagement Events
scroll_depth
Event Name: scroll_depth
Firing Pattern: Periodic – on scroll milestones
Frequency: Once per 25% increment (25%, 50%, 75%, 100%)
GTM Tag: GA4 – Scroll Depth
Description
Tracks how far users scroll down the page, providing insights into content consumption and identifying where users lose interest.
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
percent or scroll_depth | string/number | Scroll depth percentage | '75' or 75 |
page_height | number | Total page height in pixels | 3500 |
viewport_height | number | Browser viewport height | 800 |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
Milestone Triggers
The plugin fires at these specific scroll depths:
- 25% – User is browsing
- 50% – User is engaged
- 75% – User is highly interested
- 100% – User consumed full content
Technical Implementation
// Automatic firing pattern
Page loads → User scrolls to 26% → Event fires with percent: '25'
User scrolls to 51% → Event fires with percent: '50'
User scrolls to 76% → Event fires with percent: '75'
User scrolls to 100% → Event fires with percent: '100'
// Each milestone fires only ONCE per page
// Fast scrolling captures all passed milestones
DataLayer Push Structure
// Two-push pattern for GTM Preview visibility
// Push 1: Data
window.dataLayer.push({
scroll_depth: '75',
page_height: 3500,
viewport_height: 800,
session_id: 'sess_1760034620287_lyiym92mb',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'scroll_depth' });
Business Value
Use Cases:
- Content Optimization: Identify where users abandon long content
- CTA Placement: Position calls-to-action at optimal scroll depths
- Article Performance: Compare engagement across different article lengths
- Mobile vs Desktop: Analyze scroll behavior by device
Example Analysis:
-- Content engagement by scroll depth
SELECT
page_path,
COUNT(DISTINCT CASE WHEN percent >= 75 THEN user_id END) as deep_readers,
COUNT(DISTINCT user_id) as total_readers,
ROUND(100.0 * deep_readers / total_readers, 2) as completion_rate
FROM scroll_events
GROUP BY page_path
HAVING total_readers > 100
ORDER BY completion_rate DESC
ROI Impact:
- Increased content engagement by 34% by optimizing content length
- Improved conversion rates by 18% by repositioning CTAs to 50% scroll depth
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: scroll_depth
Fire on: Some Custom Events
DLV - scroll_depth | equals | 75
(Or create separate triggers for each milestone)
Variable Setup:
// Create these Data Layer Variables in GTM
Variable Name: DLV - scroll_depth
Data Layer Variable Name: scroll_depth
Variable Name: DLV - page_height
Data Layer Variable Name: page_height
scroll_back_up
Event Name: scroll_back_up
Firing Pattern: Conditional – when user scrolls up significantly
Frequency: Multiple times per page (with 5-second cooldown)
GTM Tag: GA4 – Scroll Back Up
Description
Detects when users scroll back up the page after scrolling down, indicating they’re re-reading content, searching for something, or reconsidering a decision. This is a powerful hesitation and intent signal.
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
from_percent | number | Scroll position before scrolling up | 75 |
to_percent | number | Scroll position after scrolling up | 30 |
scroll_delta | number | Distance scrolled up (pixels) | 450 |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
Detection Logic
The event triggers when:
- User has scrolled down significantly (past initial viewport)
- User scrolls up by more than 150 pixels
- Cooldown period of 5 seconds has passed since last trigger
This prevents excessive firing from small scroll adjustments.
DataLayer Push Structure
// Two-push pattern
// Push 1: Data
window.dataLayer.push({
from_percent: 75,
to_percent: 30,
scroll_delta: 450,
session_id: 'sess_1760034620287_lyiym92mb',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'scroll_back_up' });
Business Value
Use Cases:
- Purchase Hesitation Detection: User scrolling back to review pricing/features
- Comparison Shopping Signal: Re-checking product details
- Form Abandonment Warning: User reconsidering form submission
- Content Confusion Indicator: User seeking clarification they missed
Behavioral Patterns:
| From % | To % | Likely Meaning | Action |
|---|---|---|---|
| 75+ | <40 | Re-reading key info | Show comparison tool |
| 60-75 | <30 | Pricing review | Display pricing FAQ |
| 50+ | <25 | Seeking missed detail | Offer chat support |
| 100 | Any | Post-CTA review | Show testimonials |
Example Trigger:
// GTM Custom HTML Tag
if ({{Event}} === 'scroll_back_up' && {{DLV - from_percent}} > 70) {
// User scrolled back from bottom - probably comparing
// Display: "Still comparing? Here's a side-by-side chart"
showComparisonWidget();
}
ROI Impact:
- Reduced cart abandonment by 12% with timely pricing reassurance
- Increased form completions by 9% with sticky help tooltips
- Improved conversion rate by 15% with contextual comparison tools
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: scroll_back_up
Fire on: Some Custom Events
DLV - from_percent | greater than | 60
DLV - scroll_delta | greater than | 200
time_on_page
Event Name: time_on_page
Firing Pattern: Periodic – every 30 seconds
Frequency: Multiple times per page
GTM Tag: GA4 – Time On Page
Description
Tracks total elapsed time from page load, including periods when tab is in background. Provides session duration metrics but does NOT filter for active engagement.
Important: This is NOT the same as active_time. time_on_page runs even when:
- User switches to another tab
- Browser is minimized
- User walks away from computer
For true engagement measurement, use active_time instead.
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
seconds or time_seconds | number | Cumulative seconds on page | 30, 60, 90… |
page_visible | boolean | Tab currently visible | true or false |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
Firing Pattern
// Continuous 1-second timer
t=0s → Timer starts
t=30s → Event fires (seconds: 30)
t=60s → Event fires (seconds: 60)
t=90s → Event fires (seconds: 90)
...continues until page exit
// Timer runs even if tab is hidden
DataLayer Push Structure
// Two-push pattern
// Push 1: Data
window.dataLayer.push({
time_seconds: 60,
page_visible: true,
session_id: 'sess_1760034620287_lyiym92mb',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'time_on_page' });
Business Value
Use Cases:
- Session Duration Benchmarking: Compare time across pages/sources
- Engagement Rate Calculation: Compare
active_timevstime_on_page - Multi-Tab Behavior Analysis: Track background tab duration
- Content Depth Indicators: Long time = detailed content
Engagement Rate Formula:
engagement_rate = (active_time / time_on_page) * 100
// Example:
// active_time: 180 seconds
// time_on_page: 240 seconds
// engagement_rate: 75%
// This means user was actively engaged 75% of the time
Example Analysis:
-- Pages with best engagement rates
SELECT
page_path,
AVG(active_time) as avg_active,
AVG(time_on_page) as avg_total,
ROUND(100.0 * AVG(active_time) / AVG(time_on_page), 2) as engagement_rate
FROM time_metrics
GROUP BY page_path
HAVING AVG(time_on_page) > 60
ORDER BY engagement_rate DESC
Comparison with active_time
| Metric | Measures | Includes Tab Switching | Best For |
|---|---|---|---|
time_on_page | Total elapsed time | Yes | Session duration |
active_time | Active engagement only | No (pauses) | True engagement |
Recommendation: Use active_time as your primary engagement metric.
active_time (MOST IMPORTANT)
Event Name: active_time
Firing Pattern: Periodic – every 30 seconds of active engagement
Frequency: Multiple times per page (only when user is active)
GTM Tag: GA4 – Active Time
Priority: CRITICAL – This is your #1 engagement metric
Description
Tracks only the time users are actively interacting with the page through mouse movement, scrolling, typing, or clicking. This is the gold standard for measuring true engagement because it filters out:
- Background tabs
- Minimized windows
- Users who walked away
- Idle time
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
seconds or active_seconds | number | Cumulative active time | 30, 60, 90… |
page_visible | boolean | Tab currently visible | true |
interaction_rate | number | % of time active (vs total time) | 0.93 (93%) |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
Activity Detection
The timer increments only when detecting:
- Mouse movement
- Scrolling
- Keyboard input
- Clicking/tapping
- Touch interactions
The timer pauses when:
- No interaction for 2 seconds
- Tab loses focus
- Browser minimized
- User switches tabs
Firing Pattern
// Active engagement timer
User arrives → Timer ready (0s)
User scrolls → Timer starts (1s, 2s, 3s...)
User idle 2s → Timer pauses at last count
User moves mouse → Timer resumes
30s active → Event fires (seconds: 30)
60s active → Event fires (seconds: 60)
...continues until page exit
// Only counts truly active seconds
DataLayer Push Structure
// Two-push pattern
// Push 1: Data
window.dataLayer.push({
active_seconds: 60,
page_visible: true,
interaction_rate: 0.87,
session_id: 'sess_1760034620287_lyiym92mb',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'active_time' });
Why active_time is Your #1 Metric
The Problem with Traditional Time Metrics:
// Scenario: User opens your page, reads headline, switches to email
// Traditional time_on_page: 10 minutes (page was open)
// Actual active_time: 45 seconds (actually engaged)
// Engagement rate: 7.5% (terrible!)
active_time solves this:
// High-quality visitor:
// time_on_page: 5 minutes
// active_time: 4 minutes 15 seconds
// engagement_rate: 85% (excellent!)
// Low-quality visitor:
// time_on_page: 10 minutes
// active_time: 30 seconds
// engagement_rate: 5% (bounce, essentially)
Business Value
Use Cases:
- True Engagement Measurement: Filter noise from your analytics
- Quality Traffic Attribution: Weight sources by engagement quality
- Content Performance: Identify truly engaging content
- Audience Segmentation: Build “high-intent” audiences
- Attribution Modeling: Give credit based on actual attention
- Ad Performance: Measure real time spent vs. claimed views
Real-World Impact:
-- Find your highest-quality traffic sources
SELECT
utm_source,
utm_medium,
COUNT(*) as sessions,
AVG(active_time) as avg_active_seconds,
AVG(page_quality_score) as avg_quality,
SUM(conversions) as total_conversions,
ROUND(SUM(conversions) / COUNT(*) * 100, 2) as conversion_rate
FROM session_engagement_summary
GROUP BY utm_source, utm_medium
HAVING sessions > 100
ORDER BY avg_active_seconds DESC, conversion_rate DESC
ROI Examples:
- E-commerce: 67% lift in ROAS by reallocating budget to high active_time sources
- SaaS: 42% increase in trial signups by targeting users with 90+ seconds active_time
- Publishing: 3.2x ad revenue per session from high-engagement visitors
- Lead Gen: 28% more qualified leads by filtering for 60+ seconds active time
Audience Building
Create GA4 Audiences:
Audience Name: Highly Engaged Visitors
Conditions:
- active_time >= 90 seconds
- page_quality_score >= 70
- session_page_number >= 2
Use for:
- Retargeting campaigns
- Similar audience seed lists
- Personalization triggers
- Lead scoring boost
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: active_time
Fire on: Some Custom Events
DLV - active_seconds | greater than | 60
(Creates "engaged visitor" trigger for 60+ seconds)
Tag Example – GA4 Measurement:
Tag Type: Google Analytics: GA4 Event
Event Name: user_engagement
Event Parameters:
- engagement_time_msec: {{DLV - active_seconds}} * 1000
- session_engaged: 1
Trigger: active_time (fires every 30s)
focus_blur (Tab Visibility)
Event Name: focus_blur
Firing Pattern: State change – when tab visibility changes
Frequency: Multiple times per session
GTM Tag: GA4 – Focus/Blur
Description
Tracks when users switch away from your tab (blur) or return to it (focus), revealing multi-tab behavior and comparison shopping patterns.
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
visibility_state | string | Current visibility state | 'visible' or 'hidden' |
tab_switches | number | Total tab switches in session | 3 |
time_away | number | Seconds since last focus (on blur) | 45 |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
Firing Pattern
// User journey
User on your tab → visibility_state: 'visible'
User switches to competitor → Event fires: visibility_state: 'hidden'
User returns to your tab → Event fires: visibility_state: 'visible', time_away: 45
DataLayer Push Structure
// When user switches away (blur)
// Push 1: Data
window.dataLayer.push({
visibility_state: 'hidden',
tab_switches: 3,
session_id: 'sess_xxx',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'focus_blur' });
// When user returns (focus)
// Push 1: Data
window.dataLayer.push({
visibility_state: 'visible',
tab_switches: 4,
time_away: 45,
session_id: 'sess_xxx',
timestamp: '2025-01-09T14:30:15.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'focus_blur' });
Business Value
Use Cases:
- Comparison Shopping Detection: User switching between competitors
- Multi-Tab Research Behavior: Evaluating multiple options
- Active Engagement Validation: Distinguish active vs background tabs
- Attention Span Analysis: How long users stay focused
Behavioral Patterns:
| Tab Switches | Time Away | Likely Meaning | Action |
|---|---|---|---|
| 1-2 | <30s | Quick check (email) | Normal behavior |
| 3-5 | 30-120s | Comparison shopping | Show unique value props |
| 5+ | 60-180s | Heavy research | Offer comparison guide |
| Any | >300s | Abandoned | Consider win-back |
Example Trigger:
// GTM Custom HTML Tag
if ({{Event}} === 'focus_blur' &&
{{DLV - visibility_state}} === 'visible' &&
{{DLV - tab_switches}} >= 3) {
// User just returned after multiple switches
// Display: "Comparing options? Here's why we're different"
showComparisonBanner();
}
ROI Impact:
- 19% lift in conversions with comparison-triggered messaging
- 24% reduction in cart abandonment with return-visitor prompts
- 31% more email captures from multi-tab researchers
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: focus_blur
Fire on: Some Custom Events
DLV - visibility_state | equals | visible
DLV - tab_switches | greater than | 2
(Triggers when user returns after 2+ switches)
hover_intent
Event Name: hover_intent
Firing Pattern: Conditional – on significant hover over interactive elements
Frequency: Multiple times per page
GTM Tag: GA4 – Hover Intent
Availability: Premium only
Description
Tracks when users hover over buttons, CTAs, or links for 500ms or longer, indicating interest even when they don’t click. This is a powerful micro-conversion and intent signal.
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
element_type | string | Type of element hovered | 'button', 'a', 'cta' |
element_text | string | Text content of element | 'Get Started' |
element_id | string | Element ID (if present) | 'pricing-cta' |
element_classes | array | CSS classes | ['btn', 'btn-primary'] |
hover_duration | number | Hover time in milliseconds | 1500 |
clicked | boolean | Whether element was clicked | false |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
Detection Logic
The event triggers when:
- User hovers over a trackable element (buttons, links, elements with
.ctaclass) - Hover duration exceeds 500 milliseconds
- Element leaves viewport or hover ends
Tracked Elements:
- All
<button>elements - All
<a>(link) elements - Any element with class containing “cta”, “btn”, or “button”
- Custom elements marked with
data-adt-hover="true"
DataLayer Push Structure
// Two-push pattern
// Push 1: Data
window.dataLayer.push({
element_type: 'button',
element_text: 'Start Free Trial',
element_id: 'hero-cta',
element_classes: ['btn', 'btn-primary', 'cta-main'],
hover_duration: 1500,
clicked: false,
session_id: 'sess_xxx',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'hover_intent' });
Business Value
Use Cases:
- Hesitation Detection: User interested but not clicking
- CTA Optimization: Identify which CTAs get attention
- Micro-Conversion Tracking: Interest even without click
- Lead Scoring: Hover = intent signal
- A/B Test Validation: Measure attention independent of clicks
Analysis Queries:
- High Intent, Low Conversion
-- CTAs with lots of hover but few clicks SELECT element_text, COUNT(*) as total_hovers, COUNT(CASE WHEN clicked THEN 1 END) as clicks, ROUND(100.0 * COUNT(CASE WHEN clicked THEN 1 END) / COUNT(*), 2) as click_rate, AVG(hover_duration) as avg_hover_ms FROM hover_intent_events GROUP BY element_text HAVING total_hovers > 50 AND click_rate < 30 ORDER BY total_hovers DESC - Most Engaging CTAs
-- Which CTAs get longest hovers? SELECT element_text, AVG(hover_duration) as avg_hover_ms, COUNT(*) as total_hovers, COUNT(CASE WHEN clicked THEN 1 END) as clicks FROM hover_intent_events GROUP BY element_text ORDER BY avg_hover_ms DESC - Hesitation Patterns
-- Users who hover but never click SELECT session_id, COUNT(*) as hesitation_events, AVG(hover_duration) as avg_hover, MAX(hover_duration) as longest_hover FROM hover_intent_events WHERE clicked = false GROUP BY session_id HAVING hesitation_events >= 3 ORDER BY hesitation_events DESC - CTA Effectiveness Analysis
-- Which CTAs get attention but no clicks? SELECT element_text, COUNT(*) as hovers, SUM(CASE WHEN clicked THEN 1 ELSE 0 END) as clicks, AVG(hover_duration) as avg_hover_ms, ROUND(100.0 * SUM(CASE WHEN clicked THEN 1 ELSE 0 END) / COUNT(*), 2) as click_rate FROM hover_intent_events GROUP BY element_text HAVING hovers > 50 ORDER BY hovers DESC, click_rate ASC
Behavioral Patterns:
| Hover Duration | Clicked | Interpretation | Action |
|---|---|---|---|
| <1s | Yes | Quick decision | Good CTA |
| >1.5s | Yes | Thoughtful click | Good conversion |
| >1.5s | No | Hesitation | Add reassurance |
| >3s | No | Confusion | Clarify CTA text |
ROI Impact:
- 21% increase in CTA click-through by adding trust badges to high-hover, low-click buttons
- 15% improvement in conversion rate by showing pricing FAQs to users who hover over pricing CTAs without clicking
- Reduced cart abandonment by 9% with exit-intent offers to users who hovered over checkout button
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: hover_intent
Conditions:
- DLV - hover_duration | greater than | 1500
- DLV - clicked | equals | false
(For targeting hesitant users)
Session Engagement Milestone
Event Name: session_engagement_milestone
Firing Pattern: Conditional – when user reaches engagement threshold
Frequency: Once per milestone per session
GTM Tag: GA4 – Engagement Milestone
Description
Fires when users cross specific engagement thresholds, enabling real-time personalization and high-quality audience building.
Milestone Thresholds
The event fires when users reach these cumulative milestones:
| Milestone | Criteria | Meaning | Use Case |
|---|---|---|---|
| engaged_visitor | active_time >= 30s | Basic engagement | Trigger welcome offer |
| high_engagement | active_time >= 90s | Deep interest | Show demo/trial CTA |
| very_high_engagement | active_time >= 180s | Exceptional attention | Premium content access |
| quality_session | page_quality_score >= 70 | High-quality visitor | Priority support offer |
| scroll_champion | scroll_depth >= 75% | Content consumer | Related content |
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
milestone_name | string | Which milestone was reached | 'high_engagement' |
active_time | number | Current active seconds | 95 |
page_quality_score | number | Current quality score (0-100) | 78 |
scroll_depth | number | Current scroll percentage | 75 |
session_id | string | Session identifier | 'sess_xxx' |
timestamp | string | ISO timestamp | '2025-01-09T14:30:00.000Z' |
DataLayer Push Structure
// Two-push pattern
// Push 1: Data
window.dataLayer.push({
milestone_name: 'high_engagement',
active_time: 95,
page_quality_score: 78,
scroll_depth: 75,
session_id: 'sess_xxx',
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'session_engagement_milestone' });
Business Value
Use Cases:
- Real-Time Personalization: Show offers to engaged users
- Progressive Profiling: Ask for info from committed visitors
- Chat Widget Timing: Offer help at optimal moment
- Content Gating: Unlock premium content for engaged readers
- Lead Scoring: Boost score when milestones reached
Example Implementations:
1. Triggered Personalization
// GTM Custom HTML Tag
// Trigger: session_engagement_milestone
// Fires when: milestone_name = 'high_engagement'
if ({{Event}} === 'session_engagement_milestone' &&
{{DLV - milestone_name}} === 'high_engagement') {
// User just hit 90 seconds active time
// Show time-limited offer
showModal({
title: "You're engaged! Here's 20% off",
cta: "Claim Discount",
timer: 300 // 5 minutes
});
}
2. Progressive Lead Capture
// Start with email only
// After milestone: ask for more
if ({{DLV - milestone_name}} === 'quality_session') {
// User has high quality score
// Safe to ask for phone number
expandFormFields(['phone', 'company']);
}
3. Intelligent Chat Trigger
// Don't annoy low-engagement users
// Offer help to engaged visitors
if ({{DLV - milestone_name}} === 'engaged_visitor' &&
{{DLV - scroll_depth}} >= 50) {
// User is engaged and scrolling
// Good time to offer assistance
triggerChatWidget({
delay: 5000, // 5 second delay
message: "Need help finding something?"
});
}
ROI Impact:
- 37% increase in conversions with milestone-triggered offers
- 52% higher form completion when progressively profiling
- 28% more qualified leads by filtering for engagement milestones
- 3.2x chat-to-conversion rate with smart timing
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: session_engagement_milestone
Fire on: Some Custom Events
DLV - milestone_name | equals | high_engagement
(Create separate triggers for each milestone)
Recommended GA4 Tag:
Tag Type: Google Analytics: GA4 Event
Event Name: {{DLV - milestone_name}}
Event Parameters:
- engagement_time: {{DLV - active_time}}
- engagement_quality: {{DLV - page_quality_score}}
Session Engagement Summary
Event Name: session_engagement_summary
Firing Pattern: End of session – on page exit
Frequency: Once per session
GTM Tag: GA4 – Session Engagement Summary
Availability: Premium only
Description
Comprehensive engagement report that fires when users leave, providing session-level metrics for attribution, segmentation, and optimization.
Learn More: Session Management Guide
When It Fires
Triggers on these exit events:
- User closes tab/window
- User navigates to external site
- Session timeout (30 minutes idle)
- Browser back button to external referrer
- Manual trigger:
window.ADTSession.triggerExit()
Event Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
session_id | string | Session identifier | 'sess_xxx' |
session_duration | number | Total session time (seconds) | 450 |
active_time_total | number | Total active seconds | 287 |
engagement_rate | number | (active_time / session_duration) * 100 | 63.78 |
page_quality_score | number | Overall quality score (0-100) | 72 |
pages_viewed | number | Total pages in session | 5 |
scroll_depth_max | number | Deepest scroll achieved | 100 |
hover_events | number | Total hover intent events | 3 |
tab_switches | number | Times user switched tabs | 2 |
form_starts | number | Forms started | 1 |
form_submits | number | Forms completed | 1 |
video_starts | number | Videos started | 0 |
click_events | number | Total clicks tracked | 12 |
user_engaged | boolean | Met engagement threshold | true |
bounce | boolean | Single page, <30s active | false |
timestamp | string | Session end time | '2025-01-09T14:30:00.000Z' |
DataLayer Push Structure
// Two-push pattern
// Push 1: Data
window.dataLayer.push({
session_id: 'sess_1760034620287_lyiym92mb',
session_duration: 450,
active_time_total: 287,
engagement_rate: 63.78,
page_quality_score: 72,
pages_viewed: 5,
scroll_depth_max: 100,
hover_events: 3,
tab_switches: 2,
form_starts: 1,
form_submits: 1,
video_starts: 0,
click_events: 12,
user_engaged: true,
bounce: false,
timestamp: '2025-01-09T14:30:00.000Z'
});
// Push 2: Event trigger
window.dataLayer.push({ event: 'session_engagement_summary' });
Business Value
Use Cases:
- Quality-Based Attribution: Weight conversions by engagement quality
- Audience Segmentation: Build “high-quality visitor” lists
- Source Quality Analysis: Compare engagement by traffic source
- Conversion Optimization: Correlate engagement with conversion
- Content Strategy: Identify high-performing content patterns
Example Analyses:
1. Traffic Source Quality
SELECT
utm_source,
utm_medium,
COUNT(*) as sessions,
AVG(active_time_total) as avg_active_seconds,
AVG(page_quality_score) as avg_quality,
AVG(engagement_rate) as avg_engagement_pct,
SUM(CASE WHEN form_submits > 0 THEN 1 ELSE 0 END) as conversions,
ROUND(100.0 * SUM(CASE WHEN form_submits > 0 THEN 1 ELSE 0 END) / COUNT(*), 2) as conv_rate
FROM session_engagement_summary
GROUP BY utm_source, utm_medium
HAVING sessions > 100
ORDER BY avg_quality DESC, conv_rate DESC
2. Engagement vs Conversion Correlation
-- Do engaged users convert better?
SELECT
CASE
WHEN page_quality_score >= 80 THEN 'High Quality (80+)'
WHEN page_quality_score >= 60 THEN 'Medium Quality (60-79)'
WHEN page_quality_score >= 40 THEN 'Low Quality (40-59)'
ELSE 'Very Low Quality (<40)'
END as quality_tier,
COUNT(*) as sessions,
AVG(active_time_total) as avg_active_time,
SUM(form_submits) as total_conversions,
ROUND(100.0 * SUM(form_submits) / COUNT(*), 2) as conversion_rate
FROM session_engagement_summary
GROUP BY quality_tier
ORDER BY conversion_rate DESC
3. Content Performance
-- Which pages drive highest engagement?
SELECT
landing_page,
COUNT(*) as sessions,
AVG(active_time_total) as avg_active_time,
AVG(page_quality_score) as avg_quality,
AVG(pages_viewed) as avg_pages,
AVG(scroll_depth_max) as avg_scroll
FROM session_engagement_summary
WHERE pages_viewed >= 1
GROUP BY landing_page
HAVING sessions > 50
ORDER BY avg_quality DESC
ROI Impact:
- 83% improvement in ROAS by reallocating budget to high-quality sources
- 46% lift in conversion rate targeting quality_score >= 70
- 2.8x ROI on paid campaigns after quality-based bid adjustments
- 34% reduction in CAC by focusing on engaged traffic
GTM Setup
Trigger Configuration:
Trigger Type: Custom Event
Event Name: session_engagement_summary
Fire on: All Custom Events
(Fires once per session at exit)
Recommended GA4 Tag:
Tag Type: Google Analytics: GA4 Event
Event Name: session_summary
Event Parameters:
- session_duration: {{DLV - session_duration}}
- active_time: {{DLV - active_time_total}}
- quality_score: {{DLV - page_quality_score}}
- engagement_rate: {{DLV - engagement_rate}}
- pages_viewed: {{DLV - pages_viewed}}
Engagement Metrics Object
Global Access
All engagement metrics are available globally via:
window.ADTEngagement.getMetrics()
Metrics Object Structure
{
// Time Metrics
activeTimeSeconds: 147, // Active engagement time
timeOnPageSeconds: 189, // Total elapsed time
engagementRate: 77.78, // (active / total) * 100
// Scroll Metrics
maxScrollDepth: 75, // Deepest scroll %
currentScrollPercent: 65, // Current position %
scrollBackUpCount: 2, // Times scrolled up
// Interaction Metrics
clickCount: 8, // Total clicks
hoverIntentCount: 3, // Hover events
formStartCount: 1, // Forms started
formSubmitCount: 0, // Forms completed
// Attention Metrics
tabSwitchCount: 2, // Focus changes
pageVisible: true, // Currently visible
attentionScore: 8.2, // 0-10 attention rating
// Session Context
sessionId: 'sess_xxx', // Session ID
pageNumber: 3, // Page in session
sessionStartTime: 1704816000000, // Timestamp
// Quality Score
pageQualityScore: 72 // 0-100 rating
}
Accessing Metrics
Get All Metrics:
const metrics = window.ADTEngagement.getMetrics();
console.log('Active Time:', metrics.activeTimeSeconds);
console.log('Quality Score:', metrics.pageQualityScore);
Get Specific Metric:
const activeTime = window.ADTEngagement.getMetrics().activeTimeSeconds;
Check Engagement Status:
const metrics = window.ADTEngagement.getMetrics();
if (metrics.pageQualityScore >= 70 && metrics.activeTimeSeconds >= 60) {
console.log('High-quality, engaged visitor!');
// Show premium offer
}
Using in GTM
Create Custom JavaScript Variables:
// Variable Name: CJS - Active Time
// Variable Type: Custom JavaScript
function() {
return window.ADTEngagement &&
window.ADTEngagement.getMetrics().activeTimeSeconds || 0;
}
// Variable Name: CJS - Quality Score
// Variable Type: Custom JavaScript
function() {
return window.ADTEngagement &&
window.ADTEngagement.getMetrics().pageQualityScore || 0;
}
// Variable Name: CJS - Engagement Rate
// Variable Type: Custom JavaScript
function() {
return window.ADTEngagement &&
window.ADTEngagement.getMetrics().engagementRate || 0;
}
Use in Triggers:
Trigger Name: High Quality Visitor
Trigger Type: Custom Event
Event Name: session_engagement_milestone
Fires on: Some Custom Events
{{CJS - Quality Score}} | greater than | 70
{{CJS - Active Time}} | greater than | 60
Page Quality Score
Overview
The Page Quality Score (0-100) is a composite metric combining multiple engagement signals to produce a single, actionable quality rating for each session.
Calculation Formula
page_quality_score =
(engagement_rate * 0.30) + // 30% weight
(scroll_depth * 0.20) + // 20% weight
(min(interactions / 20, 1) * 20) + // 20% weight
(min(pages_viewed / 5, 1) * 15) + // 15% weight
(form_starts > 0 ? 15 : 0) // 15% weight bonus
// Cap at 100
page_quality_score = Math.min(page_quality_score, 100)
Component Breakdown
| Component | Weight | Measures | Max Points |
|---|---|---|---|
| Engagement Rate | 30% | (active_time / time_on_page) * 100 | 30 points |
| Scroll Depth | 20% | Percentage of page scrolled | 20 points |
| Interactions | 20% | Clicks, hovers (capped at 20) | 20 points |
| Pages Viewed | 15% | Multi-page sessions (capped at 5) | 15 points |
| Form Starts | 15% | Bonus for starting any form | 15 points |
Score Tiers
| Score Range | Quality Tier | Typical Behavior | Business Value |
|---|---|---|---|
| 80-100 | Excellent | Deep engagement, multiple pages, forms | Prime prospects |
| 60-79 | Good | Solid engagement, scrolled significantly | Quality visitors |
| 40-59 | Fair | Moderate engagement, some interaction | Standard traffic |
| 20-39 | Poor | Low engagement, minimal scroll | Low intent |
| 0-19 | Very Poor | Bounce-like, virtually no engagement | Filter out |
Business Applications
1. Audience Segmentation
-- Build quality-based audiences
SELECT
session_id,
page_quality_score,
CASE
WHEN page_quality_score >= 80 THEN 'VIP'
WHEN page_quality_score >= 60 THEN 'Hot Lead'
WHEN page_quality_score >= 40 THEN 'Warm Lead'
ELSE 'Cold Traffic'
END as segment
FROM session_engagement_summary
2. Attribution Weighting
-- Weight conversions by quality
SELECT
utm_source,
SUM(conversions) as raw_conversions,
SUM(conversions * (page_quality_score / 100)) as quality_weighted_conversions,
AVG(page_quality_score) as avg_quality
FROM session_engagement_summary
WHERE form_submits > 0
GROUP BY utm_source
ORDER BY quality_weighted_conversions DESC
3. Content Performance
-- Find highest-quality content
SELECT
page_path,
COUNT(*) as sessions,
AVG(page_quality_score) as avg_quality,
SUM(CASE WHEN page_quality_score >= 70 THEN 1 ELSE 0 END) as high_quality_sessions
FROM session_engagement_summary
GROUP BY page_path
HAVING sessions > 100
ORDER BY avg_quality DESC
4. Real-Time Personalization
// GTM Custom HTML Tag
const quality = {{CJS - Quality Score}};
if (quality >= 80) {
// VIP treatment
showPremiumOffer();
enablePrioritySupport();
} else if (quality >= 60) {
// Standard high-intent
showTrialCTA();
} else if (quality < 40 && quality > 0) {
// Low intent - nurture
showEducationalContent();
}
ROI Impact
Real-World Results:
- 67% lift in ROAS by filtering ad spend to quality_score >= 60 traffic
- 42% increase in sales from quality-score-based nurturing campaigns
- 3.2x conversion rate when targeting quality_score >= 70 for retargeting
- 28% reduction in CAC by eliminating quality_score < 30 sources
GTM Implementation
Create Data Layer Variable:
Variable Name: DLV - Page Quality Score
Variable Type: Data Layer Variable
Data Layer Variable Name: page_quality_score
Create Triggers:
Trigger 1: High Quality Visitor
Type: Custom Event
Event: session_engagement_milestone OR session_engagement_summary
Fires when: DLV - Page Quality Score >= 70
Trigger 2: VIP Visitor
Type: Custom Event
Event: session_engagement_milestone OR session_engagement_summary
Fires when: DLV - Page Quality Score >= 80
Implementation Examples
Example 1: Basic Engagement Tracking
Goal: Track core engagement metrics (scroll, active_time, time_on_page)
Settings to Enable:
Settings → General Tab:
- Enable Engagement Tracking: ON
- Enable Scroll Tracking: ON
- Track Active Time: ON
- Track Time on Page: ON
GTM Variables to Create:
// DLV - Active Time
Data Layer Variable Name: active_seconds
// DLV - Scroll Depth
Data Layer Variable Name: scroll_depth
// DLV - Engagement Rate
Data Layer Variable Name: engagement_rate
GTM Triggers:
Trigger 1: Engaged Visitor (30s)
Type: Custom Event
Event Name: active_time
Fires when: DLV - Active Time >= 30
Trigger 2: Highly Engaged (90s)
Type: Custom Event
Event Name: active_time
Fires when: DLV - Active Time >= 90
GTM Tags:
Tag 1: GA4 - Engaged Visitor
Type: GA4 Event
Event Name: engaged_visitor
Trigger: Engaged Visitor (30s)
Tag 2: GA4 - Highly Engaged
Type: GA4 Event
Event Name: highly_engaged_visitor
Trigger: Highly Engaged (90s)
Example 2: Real-Time Personalization
Goal: Show offers to engaged users based on milestones
Settings to Enable:
Settings → Advanced Tab:
- Enable Session Manager: ON
- Enable Engagement Milestones: ON
GTM Custom HTML Tag:
<script>
(function() {
// Wait for milestone event
if ({{Event}} === 'session_engagement_milestone' &&
{{DLV - milestone_name}} === 'high_engagement') {
// User just hit 90 seconds active time
const quality = {{DLV - page_quality_score}};
if (quality >= 70) {
// High-quality, engaged visitor
// Show premium offer
setTimeout(function() {
// Your offer modal code here
showSpecialOffer({
title: "Exclusive Offer for You!",
discount: "20% OFF",
cta: "Claim Now",
timer: 300 // 5 minutes
});
}, 2000); // 2 second delay
}
}
})();
</script>
Trigger:
Type: Custom Event
Event Name: session_engagement_milestone
Fires when: DLV - milestone_name = 'high_engagement'
Example 3: Quality-Based Retargeting
Goal: Build high-quality visitor audiences for retargeting
GA4 Audience Configuration:
Audience Name: High Quality Visitors
Scope: Sessions
Include users when:
- event_name = 'session_engagement_summary'
- page_quality_score >= 70
- active_time_total >= 60
Membership duration: 30 days
Google Ads Remarketing:
1. Link GA4 to Google Ads
2. Import audience: "High Quality Visitors"
3. Create campaign targeting this audience
4. Bid +50% vs. standard remarketing
Expected Results:
- 2-3x conversion rate vs. standard remarketing
- 30-40% lower CPA
- Higher average order value
Example 4: Content Optimization
Goal: Identify where users lose interest
GA4 Exploration:
Exploration Type: Free Form
Dimensions:
- Page path
- Scroll depth
- Event name
Metrics:
- Event count
- Active users
- Conversions
Filters:
- Event name = 'scroll_depth'
Segments:
- Converters vs. Non-Converters
Analysis Query:
-- Where do converters vs non-converters drop off?
SELECT
page_path,
scroll_depth,
SUM(CASE WHEN converted THEN 1 ELSE 0 END) as converters,
SUM(CASE WHEN NOT converted THEN 1 ELSE 0 END) as non_converters
FROM scroll_events
GROUP BY page_path, scroll_depth
ORDER BY page_path, scroll_depth
Optimization Actions:
- Move CTAs to where converters engage most
- Trim content past common drop-off points
- Add trust signals at hesitation points
- Test different content lengths
Example 5: Traffic Source Quality Analysis
Goal: Compare engagement quality by source
BigQuery SQL:
-- Quality by source/medium
SELECT
traffic_source.source as utm_source,
traffic_source.medium as utm_medium,
COUNT(DISTINCT user_pseudo_id) as users,
COUNT(DISTINCT session_id) as sessions,
AVG(CAST(JSON_EXTRACT_SCALAR(event_params, '$.page_quality_score') AS INT64)) as avg_quality,
AVG(CAST(JSON_EXTRACT_SCALAR(event_params, '$.active_time_total') AS INT64)) as avg_active_time,
SUM(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) as conversions,
ROUND(SUM(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) / COUNT(DISTINCT session_id) * 100, 2) as conv_rate
FROM `project.dataset.events_*`
WHERE event_name IN ('session_engagement_summary', 'purchase')
AND _TABLE_SUFFIX BETWEEN '20250101' AND '20250131'
GROUP BY utm_source, utm_medium
HAVING sessions > 100
ORDER BY avg_quality DESC, conv_rate DESC
Action Plan:
- Increase budget for high-quality sources (avg_quality >= 70)
- Optimize or pause low-quality sources (avg_quality < 40)
- A/B test landing pages for medium-quality sources (40-69)
- Adjust bids based on quality score in Google Ads
Best Practices
1. Prioritize active_time Over Everything
Why:
- Filters out background tabs
- Measures true attention
- Correlates strongly with conversion
- Essential for quality-based attribution
How to Use:
// Build audiences
if (active_time >= 90 && page_quality_score >= 70) {
audience = 'VIP';
} else if (active_time >= 60) {
audience = 'Engaged';
} else if (active_time >= 30) {
audience = 'Casual';
} else {
audience = 'Bounce';
}
2. Use Quality Score for Segmentation
Create Tiered Segments:
VIP (80-100):
- Premium offers
- Priority support
- Exclusive content
Hot Lead (60-79):
- Trial CTAs
- Comparison guides
- Demo requests
Warm Lead (40-59):
- Educational content
- Email capture
- Nurture campaigns
Cold Traffic (0-39):
- Generic content
- Broad awareness
- Filter from paid
3. Implement Progressive Engagement
Don’t overwhelm users immediately:
// Timeline approach
30s active → Trigger: Welcome back bar
60s active → Trigger: Chat widget
90s active → Trigger: Special offer
120s active → Trigger: Premium content access
4. Combine Multiple Signals
Example: Smart Exit Intent
// Traditional exit intent (cursor leaves viewport)
// + ADT engagement signals = better targeting
if (exitIntentDetected &&
active_time >= 45 &&
scroll_depth >= 50 &&
page_quality_score >= 60) {
// User is engaged but leaving - show offer
showExitModal();
} else {
// Low engagement user - don't annoy
// Let them go
}
5. Test Engagement Thresholds
Don’t assume – test:
Hypothesis: 60s active time is optimal for showing offer
Test variations:
A: Show offer at 30s active time
B: Show offer at 60s active time
C: Show offer at 90s active time
D: Show offer at quality_score >= 70 (time-independent)
Measure:
- Conversion rate
- Average order value
- User annoyance (exit rate after modal)
6. Layer Engagement with Other Data
Combine for Powerful Insights:
// High-value, high-engagement visitor
if (active_time >= 90 &&
customer_lifetime_value >= 500 &&
pages_viewed >= 3) {
// VIP treatment
personalizedGreeting();
prioritySupport();
freeShipping();
}
// High-engagement, first-time visitor
else if (active_time >= 90 &&
is_new_visitor &&
utm_source === 'google') {
// Great first impression - capture them
showTrialOffer();
}
7. Monitor Engagement Trends
Track Over Time:
-- Weekly engagement trends
SELECT
DATE_TRUNC(date, WEEK) as week,
AVG(page_quality_score) as avg_quality,
AVG(active_time_total) as avg_active_time,
AVG(engagement_rate) as avg_engagement_rate,
COUNT(*) as sessions
FROM session_engagement_summary
GROUP BY week
ORDER BY week DESC
Watch For:
- Declining engagement scores (content getting stale?)
- Increasing active_time (improvements working?)
- Seasonal patterns
- Source mix changes
8. Use Engagement for Attribution
Quality-Weighted Attribution:
// Traditional: All conversions equal
conversion_value = 1
// Quality-weighted: Better sessions = more credit
conversion_value = page_quality_score / 100
// Example:
// Conversion A: quality_score = 85 → value = 0.85
// Conversion B: quality_score = 45 → value = 0.45
// Total weighted conversions = 1.30 (vs. 2.0 unweighted)
This gives more credit to sources that drive engaged visitors, not just any visitors.
9. Respect User Privacy
Good Practices:
- Honor consent preferences
- Don’t track cross-domain without permission
- Store session data client-side only
- Clear session data on logout
- Provide opt-out mechanism
Compliance:
// Check consent before using engagement data for personalization
if (window.ADTConsent && window.ADTConsent.hasConsent('marketing')) {
// OK to personalize
showPersonalizedOffer();
} else {
// No consent - show generic content
showGenericContent();
}
10. Document Your Thresholds
Create Internal Guidelines:
## Engagement Threshold Standards
### VIP Tier
- Active Time: >= 120 seconds
- Quality Score: >= 80
- Pages Viewed: >= 3
- Action: Premium offers, priority support
### High Intent Tier
- Active Time: >= 90 seconds
- Quality Score: >= 70
- Pages Viewed: >= 2
- Action: Trial CTAs, demo requests
### Engaged Tier
- Active Time: >= 60 seconds
- Quality Score: >= 60
- Action: Email capture, nurture content
### Standard Tier
- Active Time: >= 30 seconds
- Action: Generic CTAs, awareness content
### Low Quality
- Active Time: < 30 seconds
- Quality Score: < 40
- Action: No personalization, filter from paid
Share this with your team so everyone uses consistent definitions.
Troubleshooting
active_time Not Firing
Problem: No active_time events appearing in dataLayer
Diagnosis:
- Check if feature is enabled:
console.log('Engagement enabled:', window.ADTData.include.engagement); // Should return '1' - Check if user is actually active:
// Active time only increments during interaction // Try scrolling, moving mouse, clicking console.log('Current active time:', window._adtActiveTime); - Check browser console for errors:
// Look for: // "ADT Engagement: Tracker initialized" // If not present, script didn't load
Solutions:
- Feature not enabled:
Settings → General → Enable Engagement Tracking: ON - User not interacting:
- User must actively scroll, click, or move mouse
- Timer doesn’t run for background tabs
- Timer pauses after 2 seconds of inactivity
- Script not loading:
- Check Network tab for 404 errors
- Clear cache and hard reload (Ctrl/Cmd + Shift + R)
- Verify JavaScript not blocked
- Consent blocking:
console.log('Consent granted:', !window.dataLayerBlocked); // Should return true
active_time Not Accumulating
Problem: active_time fires once but doesn’t increment
Diagnosis:
// Check current value
console.log('Active time:', window._adtActiveTime);
// Wait 30 seconds while actively scrolling/clicking
// Check again
console.log('Active time:', window._adtActiveTime);
// Should be higher
Common Causes:
- Page reloads between checks:
- Active time resets on page reload
- Check session persistence
- Tab not focused:
console.log('Page visible:', !document.hidden); // Should be true - Inactivity detection:
- Timer pauses after 2 seconds without interaction
- Keep moving mouse or scrolling
Solution:
// Continuously check timer is running
setInterval(function() {
console.log('Active:', window._adtActiveTime);
}, 3000);
// Then actively interact (scroll, click) and watch it increment
Engagement Rate Calculation Incorrect
Problem: engagement_rate doesn’t match expected (active_time / time_on_page) * 100
Check Values:
const metrics = window.ADTEngagement.getMetrics();
console.log('Active:', metrics.activeTimeSeconds);
console.log('Total:', metrics.timeOnPageSeconds);
console.log('Rate:', metrics.engagementRate);
// Manual calculation
const calculated = (metrics.activeTimeSeconds / metrics.timeOnPageSeconds) * 100;
console.log('Expected:', calculated);
Common Issues:
- Division by zero:
- If time_on_page = 0, engagement_rate = 0
- Page just loaded – wait for timer to start
- Timer sync issues:
- Timers may be slightly out of sync
- Check again after 60+ seconds
- Different measurement windows:
- active_time measured in 30s intervals
- time_on_page measured continuously
- Small discrepancies normal
Page Quality Score Always 0
Problem: page_quality_score showing 0 for all sessions
Diagnosis:
console.log('Metrics:', window.ADTEngagement.getMetrics());
// Check all components:
// - activeTimeSeconds: Should be > 0
// - maxScrollDepth: Should be > 0
// - clickCount: Depends on interactions
// - pageNumber: Should be >= 1
Common Causes:
- Too early – metrics not collected yet:
// Quality score needs time to accumulate // Wait 60+ seconds of active engagement // Then check again - Feature not enabled:
console.log('Session Manager:', window.ADTData.include.session_manager); // Should be '1' (Premium feature) - No user interaction:
- Score requires actual engagement
- User must scroll, click, interact
- Background tabs = 0 score
Solution:
- Wait 60+ seconds
- Actively interact with page (scroll, click)
- Check
window.ADTEngagement.getMetrics().pageQualityScore - Should be > 0 if interacting
Session Engagement Summary Not Firing
Problem: No session_engagement_summary event at session end
Diagnosis:
- Check if Session Manager enabled:
console.log('Session Manager:', window.ADTData.include.session_manager); // Must be '1' (Premium) - Check if session exists:
console.log('Session ID:', window.ADTSession.id()); // Should return valid session ID - Manually trigger exit:
window.ADTSession.triggerExit('manual_test'); // Check dataLayer immediately after console.log(window.dataLayer.slice(-5)); // Last 5 events
Common Causes:
- Premium feature not active:
- Session summaries require Premium license
- Verify license status
- Exit hooks not registered:
// Check if exit listeners present console.log('Exit hooks:', window._adtExitHooksRegistered); // Should be true - Page unload timing:
- Modern browsers restrict unload events
- Some exits may not fire summary
- Normal behavior – not all exits can be caught
Verification:
// Test explicitly
window.ADTSession.triggerExit('test');
const recent = window.dataLayer.filter(e => e.event === 'session_engagement_summary');
console.log('Summary events:', recent);
scroll_back_up Not Firing
Problem: Users scrolling up but no events
Diagnosis:
- Check scroll threshold:
- Must scroll UP more than 150 pixels
- Small scroll adjustments won’t trigger
- Check cooldown:
- 5-second cooldown between events
- Wait 5+ seconds after first trigger
- Scroll enough to trigger:
// Scroll down to 70%+ of page // Then scroll back up significantly (>150px) // Check dataLayer window.dataLayer.filter(e => e.event === 'scroll_back_up');
Common Causes:
- Short page:
- Page height < 1000px may not have enough scroll range
- 150px threshold may be significant % of page
- Not scrolling far enough initially:
- Must scroll down past initial viewport first
- Then scroll up > 150px
- Feature disabled:
console.log('Scroll tracking:', window.ADTData.include.scroll); // Should be '1'
hover_intent Not Firing
Problem: Hovering over buttons but no events
Diagnosis:
- Check Premium status:
console.log('Is Premium:', window.ADTData.isPremiumUser); // Must be true (Premium feature) - Check hover duration:
- Must hover 500ms+ to trigger
- Quick mouse-overs won’t fire
- Check element is trackable:
// Trackable elements: // - <button> // - <a> (links) // - Elements with 'cta', 'btn', or 'button' in class // Check if your element qualifies const el = document.querySelector('.your-button'); console.log('Tag:', el.tagName); console.log('Classes:', el.className); - Manually test:
- Hover over a clear
<button>element - Hold for 2+ seconds
- Check dataLayer:
window.dataLayer.filter(e => e.event === 'hover_intent'); - Hover over a clear
Solutions:
- Mark custom elements:
<!-- Add data attribute to make any element trackable --> <div class="custom-cta" data-adt-hover="true"> Click Here </div> - Increase hover time:
- Hold hover longer (2+ seconds to be safe)
- Verify Premium:
- Check license in plugin settings
- Free version doesn’t have hover_intent
Metrics Not Persisting Across Pages
Problem: active_time resets to 0 on navigation
Expected Behavior:
active_time(individual event) resets per page (correct)active_time_total(in session summary) persists across pages (session-level)
Check:
// Current page active time
console.log('Page active time:', window._adtActiveTime);
// Session total (if Session Manager enabled)
console.log('Session active time:', window.ADTSession.getMetrics().activeTimeTotal);
Verify on navigation:
// Before navigating
console.log('Before nav - Active time:', window._adtActiveTime);
// After navigating to new page
// Wait for page load
console.log('After nav - Active time:', window._adtActiveTime);
// Should be same or higher
Page Quality Score Always Low
Problem: All sessions showing quality scores < 40
Cause: Unrealistic expectations or tracking issue
Check:
- Are users actually engaging?
// Check raw metrics
window.ADTEngagement.getMetrics();
// Look at:
// - maxScrollDepth: Should be > 0
// - timeOnPageSeconds: Should be > 0
// - attentionScore: Should be > 0
- Formula expectations:
// Calculate manually
const engagement_rate = (active_time / time_on_page) * 100;
const score = (engagement_rate * 0.30) +
(scroll_depth * 0.20) +
// ... rest of formula
console.log('Expected score:', score);
- Realistic scoring:
- Average website: 40-60 quality score
- Good website: 60-75 quality score
- Excellent website: 75-90 quality score
- 90-100 is rare (highly engaging content)
- Consider your content:
- Short pages → Lower scroll depth → Lower score
- Quick-answer pages → Low active time → Lower score
- Background browsing → Low engagement rate → Lower score
Summary
Engagement tracking in Advanced DataLayer Tracker provides:
- True engagement measurement with
active_time(not just tab-open time) - User intent signals through scroll behavior and hover patterns
- Session quality scoring to identify high-value visitors
- Real-time milestones for personalization opportunities
- Cross-page metric persistence for accurate session summaries
- Multi-signal behavioral analysis combining scroll, time, and interactions
Key Takeaway: Use active_time and page_quality_score as your primary engagement metrics. They filter out noise and reveal true user interest, enabling data-driven optimization and ROI-positive targeting.
Quick Reference
Most Important Events
- active_time – #1 engagement metric
- session_engagement_summary – Session quality report
- scroll_depth – Content consumption
- session_engagement_milestone – Real-time opportunities
Most Important Metrics
- active_time – Seconds of true engagement
- page_quality_score – 0-100 session quality
- engagement_rate – (active_time / time_on_page) * 100
- scroll_depth – Content consumption percentage
Debug Commands
// Check status
window.ADTEngagement.status();
// Get current metrics
window.ADTEngagement.getMetrics();
// Check what's enabled
console.log(window.ADTData.include.engagement);
// Enable debug mode
window.ADTData.debug_mode = '1';
Additional Resources
- Session Management Guide
- Complete Event Documentation
- Quick Start Guide
- GTM Setup Guide
- Knowledge Base Home
Last Updated: October 22, 2025
Document Version: 2.0 (Professional Edition)