Engagement Tracking Guide

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

ParameterTypeDescriptionExample
percent or scroll_depthstring/numberScroll depth percentage'75' or 75
page_heightnumberTotal page height in pixels3500
viewport_heightnumberBrowser viewport height800
session_idstringSession identifier'sess_xxx'
timestampstringISO 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:

  1. Content Optimization: Identify where users abandon long content
  2. CTA Placement: Position calls-to-action at optimal scroll depths
  3. Article Performance: Compare engagement across different article lengths
  4. 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

ParameterTypeDescriptionExample
from_percentnumberScroll position before scrolling up75
to_percentnumberScroll position after scrolling up30
scroll_deltanumberDistance scrolled up (pixels)450
session_idstringSession identifier'sess_xxx'
timestampstringISO timestamp'2025-01-09T14:30:00.000Z'

Detection Logic

The event triggers when:

  1. User has scrolled down significantly (past initial viewport)
  2. User scrolls up by more than 150 pixels
  3. 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:

  1. Purchase Hesitation Detection: User scrolling back to review pricing/features
  2. Comparison Shopping Signal: Re-checking product details
  3. Form Abandonment Warning: User reconsidering form submission
  4. Content Confusion Indicator: User seeking clarification they missed

Behavioral Patterns:

From %To %Likely MeaningAction
75+<40Re-reading key infoShow comparison tool
60-75<30Pricing reviewDisplay pricing FAQ
50+<25Seeking missed detailOffer chat support
100AnyPost-CTA reviewShow 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

ParameterTypeDescriptionExample
seconds or time_secondsnumberCumulative seconds on page30, 60, 90
page_visiblebooleanTab currently visibletrue or false
session_idstringSession identifier'sess_xxx'
timestampstringISO 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:

  1. Session Duration Benchmarking: Compare time across pages/sources
  2. Engagement Rate Calculation: Compare active_time vs time_on_page
  3. Multi-Tab Behavior Analysis: Track background tab duration
  4. 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

MetricMeasuresIncludes Tab SwitchingBest For
time_on_pageTotal elapsed timeYesSession duration
active_timeActive engagement onlyNo (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

ParameterTypeDescriptionExample
seconds or active_secondsnumberCumulative active time30, 60, 90
page_visiblebooleanTab currently visibletrue
interaction_ratenumber% of time active (vs total time)0.93 (93%)
session_idstringSession identifier'sess_xxx'
timestampstringISO 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:

  1. True Engagement Measurement: Filter noise from your analytics
  2. Quality Traffic Attribution: Weight sources by engagement quality
  3. Content Performance: Identify truly engaging content
  4. Audience Segmentation: Build “high-intent” audiences
  5. Attribution Modeling: Give credit based on actual attention
  6. 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

ParameterTypeDescriptionExample
visibility_statestringCurrent visibility state'visible' or 'hidden'
tab_switchesnumberTotal tab switches in session3
time_awaynumberSeconds since last focus (on blur)45
session_idstringSession identifier'sess_xxx'
timestampstringISO 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:

  1. Comparison Shopping Detection: User switching between competitors
  2. Multi-Tab Research Behavior: Evaluating multiple options
  3. Active Engagement Validation: Distinguish active vs background tabs
  4. Attention Span Analysis: How long users stay focused

Behavioral Patterns:

Tab SwitchesTime AwayLikely MeaningAction
1-2<30sQuick check (email)Normal behavior
3-530-120sComparison shoppingShow unique value props
5+60-180sHeavy researchOffer comparison guide
Any>300sAbandonedConsider 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

ParameterTypeDescriptionExample
element_typestringType of element hovered'button', 'a', 'cta'
element_textstringText content of element'Get Started'
element_idstringElement ID (if present)'pricing-cta'
element_classesarrayCSS classes['btn', 'btn-primary']
hover_durationnumberHover time in milliseconds1500
clickedbooleanWhether element was clickedfalse
session_idstringSession identifier'sess_xxx'
timestampstringISO timestamp'2025-01-09T14:30:00.000Z'

Detection Logic

The event triggers when:

  1. User hovers over a trackable element (buttons, links, elements with .cta class)
  2. Hover duration exceeds 500 milliseconds
  3. 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:

  1. Hesitation Detection: User interested but not clicking
  2. CTA Optimization: Identify which CTAs get attention
  3. Micro-Conversion Tracking: Interest even without click
  4. Lead Scoring: Hover = intent signal
  5. A/B Test Validation: Measure attention independent of clicks

Analysis Queries:

  1. 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
  2. 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
  3. 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
  4. 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 DurationClickedInterpretationAction
<1sYesQuick decisionGood CTA
>1.5sYesThoughtful clickGood conversion
>1.5sNoHesitationAdd reassurance
>3sNoConfusionClarify 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:

MilestoneCriteriaMeaningUse Case
engaged_visitoractive_time >= 30sBasic engagementTrigger welcome offer
high_engagementactive_time >= 90sDeep interestShow demo/trial CTA
very_high_engagementactive_time >= 180sExceptional attentionPremium content access
quality_sessionpage_quality_score >= 70High-quality visitorPriority support offer
scroll_championscroll_depth >= 75%Content consumerRelated content

Event Parameters

ParameterTypeDescriptionExample
milestone_namestringWhich milestone was reached'high_engagement'
active_timenumberCurrent active seconds95
page_quality_scorenumberCurrent quality score (0-100)78
scroll_depthnumberCurrent scroll percentage75
session_idstringSession identifier'sess_xxx'
timestampstringISO 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:

  1. Real-Time Personalization: Show offers to engaged users
  2. Progressive Profiling: Ask for info from committed visitors
  3. Chat Widget Timing: Offer help at optimal moment
  4. Content Gating: Unlock premium content for engaged readers
  5. 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

ParameterTypeDescriptionExample
session_idstringSession identifier'sess_xxx'
session_durationnumberTotal session time (seconds)450
active_time_totalnumberTotal active seconds287
engagement_ratenumber(active_time / session_duration) * 10063.78
page_quality_scorenumberOverall quality score (0-100)72
pages_viewednumberTotal pages in session5
scroll_depth_maxnumberDeepest scroll achieved100
hover_eventsnumberTotal hover intent events3
tab_switchesnumberTimes user switched tabs2
form_startsnumberForms started1
form_submitsnumberForms completed1
video_startsnumberVideos started0
click_eventsnumberTotal clicks tracked12
user_engagedbooleanMet engagement thresholdtrue
bouncebooleanSingle page, <30s activefalse
timestampstringSession 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:

  1. Quality-Based Attribution: Weight conversions by engagement quality
  2. Audience Segmentation: Build “high-quality visitor” lists
  3. Source Quality Analysis: Compare engagement by traffic source
  4. Conversion Optimization: Correlate engagement with conversion
  5. 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

ComponentWeightMeasuresMax Points
Engagement Rate30%(active_time / time_on_page) * 10030 points
Scroll Depth20%Percentage of page scrolled20 points
Interactions20%Clicks, hovers (capped at 20)20 points
Pages Viewed15%Multi-page sessions (capped at 5)15 points
Form Starts15%Bonus for starting any form15 points

Score Tiers

Score RangeQuality TierTypical BehaviorBusiness Value
80-100ExcellentDeep engagement, multiple pages, formsPrime prospects
60-79GoodSolid engagement, scrolled significantlyQuality visitors
40-59FairModerate engagement, some interactionStandard traffic
20-39PoorLow engagement, minimal scrollLow intent
0-19Very PoorBounce-like, virtually no engagementFilter 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:

  1. Increase budget for high-quality sources (avg_quality >= 70)
  2. Optimize or pause low-quality sources (avg_quality < 40)
  3. A/B test landing pages for medium-quality sources (40-69)
  4. 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:

  1. Check if feature is enabled: console.log('Engagement enabled:', window.ADTData.include.engagement); // Should return '1'
  2. Check if user is actually active: // Active time only increments during interaction // Try scrolling, moving mouse, clicking console.log('Current active time:', window._adtActiveTime);
  3. Check browser console for errors: // Look for: // "ADT Engagement: Tracker initialized" // If not present, script didn't load

Solutions:

  1. Feature not enabled: Settings → General → Enable Engagement Tracking: ON
  2. 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
  3. Script not loading:
    • Check Network tab for 404 errors
    • Clear cache and hard reload (Ctrl/Cmd + Shift + R)
    • Verify JavaScript not blocked
  4. 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:

  1. Page reloads between checks:
    • Active time resets on page reload
    • Check session persistence
  2. Tab not focused: console.log('Page visible:', !document.hidden); // Should be true
  3. 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:

  1. Division by zero:
    • If time_on_page = 0, engagement_rate = 0
    • Page just loaded – wait for timer to start
  2. Timer sync issues:
    • Timers may be slightly out of sync
    • Check again after 60+ seconds
  3. 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:

  1. Too early – metrics not collected yet: // Quality score needs time to accumulate // Wait 60+ seconds of active engagement // Then check again
  2. Feature not enabled: console.log('Session Manager:', window.ADTData.include.session_manager); // Should be '1' (Premium feature)
  3. 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:

  1. Check if Session Manager enabled: console.log('Session Manager:', window.ADTData.include.session_manager); // Must be '1' (Premium)
  2. Check if session exists: console.log('Session ID:', window.ADTSession.id()); // Should return valid session ID
  3. Manually trigger exit: window.ADTSession.triggerExit('manual_test'); // Check dataLayer immediately after console.log(window.dataLayer.slice(-5)); // Last 5 events

Common Causes:

  1. Premium feature not active:
    • Session summaries require Premium license
    • Verify license status
  2. Exit hooks not registered: // Check if exit listeners present console.log('Exit hooks:', window._adtExitHooksRegistered); // Should be true
  3. 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:

  1. Check scroll threshold:
    • Must scroll UP more than 150 pixels
    • Small scroll adjustments won’t trigger
  2. Check cooldown:
    • 5-second cooldown between events
    • Wait 5+ seconds after first trigger
  3. 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:

  1. Short page:
    • Page height < 1000px may not have enough scroll range
    • 150px threshold may be significant % of page
  2. Not scrolling far enough initially:
    • Must scroll down past initial viewport first
    • Then scroll up > 150px
  3. 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:

  1. Check Premium status: console.log('Is Premium:', window.ADTData.isPremiumUser); // Must be true (Premium feature)
  2. Check hover duration:
    • Must hover 500ms+ to trigger
    • Quick mouse-overs won’t fire
  3. 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);
  4. Manually test:
    • Hover over a clear <button> element
    • Hold for 2+ seconds
    • Check dataLayer:
    window.dataLayer.filter(e => e.event === 'hover_intent');

Solutions:

  1. Mark custom elements: <!-- Add data attribute to make any element trackable --> <div class="custom-cta" data-adt-hover="true"> Click Here </div>
  2. Increase hover time:
    • Hold hover longer (2+ seconds to be safe)
  3. 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:

  1. Are users actually engaging?
// Check raw metrics
window.ADTEngagement.getMetrics();
// Look at:
// - maxScrollDepth: Should be > 0
// - timeOnPageSeconds: Should be > 0
// - attentionScore: Should be > 0
  1. 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);
  1. 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)
  2. 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

  1. active_time – #1 engagement metric
  2. session_engagement_summary – Session quality report
  3. scroll_depth – Content consumption
  4. session_engagement_milestone – Real-time opportunities

Most Important Metrics

  1. active_time – Seconds of true engagement
  2. page_quality_score – 0-100 session quality
  3. engagement_rate – (active_time / time_on_page) * 100
  4. 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

Last Updated: October 22, 2025
Document Version: 2.0 (Professional Edition)

Was this article helpful?