Overview
Chartbeat uses a unique event model that differs from traditional event-based analytics platforms. Instead of relying on discrete custom events, Chartbeat continuously monitors page context and user engagement through regular "heartbeat" pings. Understanding this model is critical for successful implementation and accurate measurement.
Chartbeat's Event Philosophy
Context Over Events: Chartbeat prioritizes continuous engagement measurement over discrete action tracking. The platform:
- Sends periodic heartbeat pings (every 15 seconds by default)
- Monitors user activity indicators (mouse movement, scrolling, window focus)
- Tracks engaged time rather than just page views
- Measures content attention rather than simple clicks
When to Use Events: While Chartbeat primarily tracks context, certain scenarios require event-like triggers:
- Virtual page views in single-page applications (SPAs)
- Video player interactions (play, pause, progress, complete)
- Headline testing impressions and clicks
- Custom engagement milestones or interactions
Event Model
The Heartbeat System
Chartbeat's core tracking mechanism is the "heartbeat" - a regular ping that carries:
Ping Structure:
// Sample heartbeat ping payload
{
uid: 12345, // Account ID
h: 'example.com', // Domain (host)
p: '/news/politics/article', // Path
t: 'Article Title', // Title
a: 'Jane Smith', // Author
s: 'News,Politics', // Sections
r: 'https://google.com', // Referrer
c: 45, // Concurrent users (returned from server)
i: 1, // Active indicator (user engaged)
w: 1024, // Window width
x: 500, // Scroll position
v: '2.0', // Script version
b: 1 // Bounce flag
}
Engagement Indicators: Chartbeat determines if a user is "engaged" based on:
- Mouse movement within the last 5 seconds
- Keyboard input within the last 5 seconds
- Window/tab has focus
- User is scrolling
- Video is playing (if video tracking enabled)
Inactive Users: If none of these indicators are present, the heartbeat still fires but with i: 0, indicating the user is idle.
Virtual Page Views (SPA Events)
For single-page applications, virtual page views function as route change events:
// Trigger virtual page view
pSUPERFLY.virtualPage({
sections: 'News,Politics',
authors: 'Jane Smith',
title: 'New Article Title',
path: '/news/politics/new-article'
});
What Happens:
- Engagement timer continues (doesn't reset unless specified)
- Metadata updates for subsequent heartbeats
- New page context reflected in Real-Time dashboard
- User journey tracking maintained across navigation
Best Practices:
- Call
virtualPage()immediately after route change - Update all metadata fields to reflect new content
- Don't call on initial page load (standard tracking handles this)
- Ensure data layer updates before virtualPage fires
Video Engagement Events
When Chartbeat's video module is enabled, it tracks specific video interactions:
Video Events Tracked:
| Event | Trigger | Data Collected |
|---|---|---|
video_start |
User initiates playback | Video ID, title, duration |
video_pause |
User pauses video | Current position, time watched |
video_resume |
User resumes after pause | Resume position |
video_complete |
Video reaches 95%+ completion | Total watch time, completion rate |
video_milestone |
25%, 50%, 75% thresholds | Milestone percentage, time to milestone |
Implementation Example:
// Initialize Chartbeat video tracking
var videoTracker = pSUPERFLY.video({
videoId: 'video-123',
title: 'Breaking News Video',
duration: 180 // seconds
});
// Track play event
videoTracker.play();
// Track pause event
videoTracker.pause(currentTime);
// Track completion
videoTracker.complete();
Headline Testing Events
Chartbeat's headline testing (MAB - Multi-Armed Bandit) fires events for:
Impression Event: When headline variant is shown
// Automatically tracked by Chartbeat
// Impression logged when page loads with variant
Click Event: When user clicks headline to view content
// Automatically tracked when user navigates to article
// Click attributed to winning headline variant
Configuration:
var _sf_async_config = {
uid: 12345,
domain: 'example.com',
mabAllow: true, // Enable headline testing
mabPriority: 1 // Test priority level
};
Core Events to Ship
1. Initial Page Load (Pageview)
Event Type: Automatic page view Trigger: When Chartbeat script initializes Data Required: uid, domain, path, sections, authors, title
Implementation:
var _sf_async_config = {
uid: 12345,
domain: 'example.com',
sections: 'News,Politics',
authors: 'Jane Smith',
title: 'Article Headline',
path: '/news/politics/article',
useCanonical: true,
useCanonicalDomain: true
};
Validation:
- Check Network tab for initial ping to
ping.chartbeat.net - Verify all metadata fields present in ping query parameters
- Confirm Real-Time dashboard shows the pageview
2. Heartbeat Pings (Engagement Tracking)
Event Type: Periodic engagement ping Trigger: Every 15 seconds (default interval) Data Collected: Engagement status, scroll position, window dimensions
What Gets Tracked:
- Active time (engaged vs. idle)
- Scroll depth and reading progress
- Concurrent users on same content
- User geography and traffic source
Validation:
- Monitor Network tab for pings every 15 seconds
- Check
iparameter:1= engaged,0= idle - Verify scroll position (
xparameter) updates as user scrolls
3. Virtual Page Views (SPA Navigation)
Event Type: Manual virtual pageview Trigger: Route change in single-page application Data Required: Updated sections, authors, title, path
Implementation Patterns:
React Router:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function useChartbeatVirtualPage(pageData) {
const location = useLocation();
useEffect(() => {
if (typeof pSUPERFLY !== 'undefined') {
pSUPERFLY.virtualPage({
sections: pageData.sections,
authors: pageData.authors,
title: pageData.title,
path: location.pathname
});
}
}, [location, pageData]);
}
Vue Router:
// In main router file
router.afterEach((to, from) => {
if (typeof pSUPERFLY !== 'undefined') {
pSUPERFLY.virtualPage({
sections: to.meta.sections,
authors: to.meta.authors,
title: to.meta.title || document.title,
path: to.path
});
}
});
Angular Router:
// In app.component.ts
constructor(private router: Router) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
if (typeof (window as any).pSUPERFLY !== 'undefined') {
(window as any).pSUPERFLY.virtualPage({
sections: this.getSections(),
authors: this.getAuthors(),
title: document.title,
path: event.urlAfterRedirects
});
}
});
}
Validation:
- Navigate between routes in your SPA
- Check Real-Time dashboard updates to show new page
- Verify engagement timer continues (doesn't reset to 0)
- Inspect Network tab for new ping with updated metadata
4. Video Engagement (Optional)
Event Type: Video interaction tracking Trigger: User plays, pauses, or completes video Data Required: Video ID, title, duration, current position
Full Implementation:
// Video tracking class
class ChartbeatVideoTracker {
constructor(videoElement, videoId, videoTitle) {
this.video = videoElement;
this.videoId = videoId;
this.videoTitle = videoTitle;
this.tracker = null;
this.init();
}
init() {
// Initialize Chartbeat video tracker
this.tracker = pSUPERFLY.video({
videoId: this.videoId,
title: this.videoTitle,
duration: this.video.duration
});
// Attach event listeners
this.video.addEventListener('play', () => this.onPlay());
this.video.addEventListener('pause', () => this.onPause());
this.video.addEventListener('ended', () => this.onComplete());
this.video.addEventListener('timeupdate', () => this.onProgress());
}
onPlay() {
this.tracker.play();
console.log('Chartbeat: Video play tracked');
}
onPause() {
this.tracker.pause(this.video.currentTime);
console.log('Chartbeat: Video pause tracked at', this.video.currentTime);
}
onComplete() {
this.tracker.complete();
console.log('Chartbeat: Video completion tracked');
}
onProgress() {
// Track milestones at 25%, 50%, 75%, 95%
const progress = (this.video.currentTime / this.video.duration) * 100;
// Milestone tracking handled automatically by Chartbeat
}
}
// Usage
const video = document.querySelector('#my-video');
const tracker = new ChartbeatVideoTracker(
video,
'video-123',
'Breaking News Video Report'
);
Validation:
- Play video and check for play event in Network tab
- Pause and verify pause event fires
- Complete video and confirm completion tracking
- Check Chartbeat video dashboard for engagement metrics
5. Headline Testing (Optional)
Event Type: Headline variant impression and click Trigger: Automatically when MAB is enabled Configuration Required: mabAllow: true
Setup:
var _sf_async_config = {
uid: 12345,
domain: 'example.com',
mabAllow: true,
mabPriority: 1,
sections: 'News,Politics',
authors: 'Jane Smith',
title: 'Original Headline', // Will be replaced with variant
path: '/news/politics/article'
};
How It Works:
- Chartbeat selects headline variant based on algorithm
- Variant displayed to user (replaces original title)
- Impression logged automatically
- If user clicks, click event attributed to variant
- Algorithm optimizes for engagement over time
Payload Rules
Required Payload Fields
Every heartbeat ping must include:
| Field | Parameter | Format | Example |
|---|---|---|---|
| UID | u |
Integer | 12345 |
| Domain | h |
String | example.com |
| Path | p |
String | /news/politics/article |
| Title | t |
String | Article+Headline |
| Authors | a |
String | Jane+Smith |
| Sections | s |
String | News,Politics |
| Referrer | r |
String | https://google.com |
Data Consistency Rules
1. Stable Identifiers: Keep core identifiers consistent for longitudinal reporting
- Use canonical paths, not variant URLs
- Standardize author names across all content
- Maintain consistent section taxonomy
2. Hierarchical Sections: Build sections from general to specific
// Correct
sections: 'News,Politics,Elections'
// Incorrect (flat taxonomy loses context)
sections: 'Elections'
3. Referrer Preservation: Maintain accurate referrer for traffic attribution
// Capture original referrer on landing
var originalReferrer = document.referrer;
// Pass to Chartbeat config
_sf_async_config.r = originalReferrer;
4. Title Formatting: Use clean, readable titles
// Good
title: 'Election Results Show Record Turnout'
// Bad (includes site branding, too long)
title: 'Election Results Show Record Turnout | Example News | Breaking News Updates 2025'
Payload Size Optimization
Keep payloads lean for performance:
- Limit section depth to 3-4 levels
- Keep titles under 100 characters
- Avoid redundant metadata
- Use canonical URLs (shorter than full URLs with tracking params)
Naming & Conventions
Section Naming Standards
Format: PrimaryCategory,Subcategory,SpecificTopic
Examples:
// News content
'News,Politics,Elections'
'News,Local,Crime'
'News,World,Europe'
// Sports content
'Sports,Football,NFL'
'Sports,Basketball,NBA'
'Sports,Olympics,Summer2024'
// Entertainment
'Entertainment,Movies,Reviews'
'Entertainment,Music,Concerts'
// Special sections
'Opinion,Editorials'
'Sponsored,BrandedContent'
Avoid:
- URLs as section names:
/news/politics/→ UseNews,Politics - Abbreviations:
Pol→ UsePolitics - Inconsistent casing:
news,Politics→ UseNews,Politics
Author Naming Standards
Format: First Last (comma-separated for multiple authors)
Examples:
// Single author
authors: 'Jane Smith'
// Multiple authors
authors: 'Jane Smith,John Doe'
// Staff/Editorial
authors: 'Staff Writer'
authors: 'Editorial Board'
// Wire service
authors: 'Associated Press'
Code Examples
Complete Implementation Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Article Title</title>
</head>
<body>
<!-- Page content -->
<article>
<h1>Breaking News: Election Results</h1>
<p class="byline">By Jane Smith</p>
<p>Article content...</p>
</article>
<!-- Chartbeat Tracking -->
<script type="text/javascript">
// Configuration
var _sf_async_config = {
uid: 12345,
domain: 'example.com',
useCanonical: true,
useCanonicalDomain: true,
sections: 'News,Politics,Elections',
authors: 'Jane Smith',
title: 'Breaking News: Election Results',
path: '/news/politics/election-results',
// Optional: Video tracking
videoPluginEnabled: true,
// Optional: Headline testing
mabAllow: true
};
// Load Chartbeat script
(function() {
window._sf_endpt = (new Date()).getTime();
var e = document.createElement('script');
e.src = '//static.chartbeat.com/js/chartbeat.js';
e.async = true;
document.getElementsByTagName('head')[0].appendChild(e);
})();
</script>
</body>
</html>
SPA with Virtual Page Views
// App.js - React example with routing
import { useEffect } from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
// Initialize Chartbeat on mount
window._sf_async_config = {
uid: 12345,
domain: 'example.com',
useCanonical: true
};
// Load script
const script = document.createElement('script');
script.src = '//static.chartbeat.com/js/chartbeat.js';
script.async = true;
document.head.appendChild(script);
}, []);
useEffect(() => {
// Trigger virtual page view on route change
if (typeof window.pSUPERFLY !== 'undefined') {
// Get page metadata from your data source
const pageMetadata = getPageMetadata(location.pathname);
window.pSUPERFLY.virtualPage({
sections: pageMetadata.sections,
authors: pageMetadata.authors,
title: pageMetadata.title,
path: location.pathname
});
}
}, [location]);
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/article/:id" element={<Article />} />
</Routes>
);
}
Validation Steps
Pre-Deployment Checklist
- Chartbeat configuration object defined with all required fields
- Script loads asynchronously without blocking page render
- No JavaScript errors in console
- Data layer populated before Chartbeat initialization
- Virtual page view implementation tested (if SPA)
- Video tracking configured (if applicable)
Post-Deployment Testing
1. Initial Pageview Validation
// Open DevTools Console
// Check configuration
console.log(_sf_async_config);
// Check script loaded
console.log(typeof pSUPERFLY !== 'undefined' ? 'Loaded' : 'Not loaded');
2. Network Request Validation
- Open DevTools Network tab
- Filter for
chartbeat.net - Verify initial ping contains all metadata
- Check heartbeat pings fire every 15 seconds
3. Real-Time Dashboard Validation
- Open Chartbeat Real-Time dashboard
- Navigate to your test page
- Verify page appears in "Now" section
- Check metadata accuracy (section, author, title)
- Confirm engagement timer increments
4. SPA Virtual Page View Testing
// In console, manually trigger virtual page
pSUPERFLY.virtualPage({
sections: 'Test,Section',
authors: 'Test Author',
title: 'Test Title',
path: '/test/path'
});
// Check Real-Time dashboard updates
// Verify engagement timer continues (doesn't reset)
QA Notes
SPA Navigation Testing
Test Scenarios:
Forward Navigation: Click link to new page
- Verify virtualPage fires
- Check metadata updates in Real-Time
- Confirm engagement timer continues
Back Button: Use browser back button
- Verify virtualPage fires on popstate
- Check metadata reverts to previous page
- Confirm no duplicate tracking
Direct Navigation: Type URL in address bar
- Verify standard pageview (not virtual)
- Check correct metadata loads
Common Issues:
// Problem: Virtual page fires on initial load
// Solution: Only call after route change, not on mount
// Bad
useEffect(() => {
pSUPERFLY.virtualPage({ ... }); // Fires on mount!
}, []);
// Good
useEffect(() => {
if (location.pathname !== initialPath) {
pSUPERFLY.virtualPage({ ... });
}
}, [location]);
Paywall and Overlay Testing
Test Paywall Scenarios:
- Ensure paywall overlays don't break tracking
- Verify referrer not overwritten by paywall redirect
- Check metadata remains accurate through paywall flow
Example Issue:
// Problem: Paywall redirects overwrite original referrer
// Solution: Capture referrer before redirect
// On landing page (before paywall)
sessionStorage.setItem('original_referrer', document.referrer);
// After paywall (when tracking resumes)
_sf_async_config.r = sessionStorage.getItem('original_referrer') || document.referrer;
Campaign Overlay Testing
Test Campaign Scenarios:
Validation:
- Overlays don't trigger false engagement
- Section/author metadata not affected
- Heartbeat continues during overlay display
Troubleshooting Table
| Issue | Symptoms | Diagnosis | Resolution |
|---|---|---|---|
| No heartbeats | No pings in Network tab | Script not loading | Check script URL, CSP policies, ad blockers |
| Engagement not tracking | i=0 in all pings |
Engagement detection broken | Verify mouse/keyboard events working, check iframe issues |
| Virtual pages not firing | Same page in Real-Time after navigation | virtualPage not called | Implement router integration, check pSUPERFLY availability |
| Metadata incorrect | Wrong section/author in dashboard | Config mismatch | Validate data layer, check variable mapping |
| Engagement resets | Timer restarts on each navigation | virtualPage creating new session | Don't reinitialize tracker, just call virtualPage |
| Duplicate pings | Multiple pings per heartbeat | Multiple script instances | Remove duplicate script tags, check TMS configuration |
| Video not tracking | No video events in dashboard | Video module not enabled | Set videoPluginEnabled: true, initialize video tracker |
| Referrer lost | Direct traffic instead of social | Referrer not preserved | Capture and pass referrer explicitly in config |