Chartbeat Event Tracking | OpsBlu Docs

Chartbeat Event Tracking

How to implement custom event tracking in Chartbeat. Covers event naming conventions, required and optional parameters, ecommerce events, debugging with.

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:

  1. Engagement timer continues (doesn't reset unless specified)
  2. Metadata updates for subsequent heartbeats
  3. New page context reflected in Real-Time dashboard
  4. 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 i parameter: 1 = engaged, 0 = idle
  • Verify scroll position (x parameter) 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:

  1. Chartbeat selects headline variant based on algorithm
  2. Variant displayed to user (replaces original title)
  3. Impression logged automatically
  4. If user clicks, click event attributed to variant
  5. 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/ → Use News,Politics
  • Abbreviations: Pol → Use Politics
  • Inconsistent casing: news,Politics → Use News,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:

  1. Forward Navigation: Click link to new page

    • Verify virtualPage fires
    • Check metadata updates in Real-Time
    • Confirm engagement timer continues
  2. Back Button: Use browser back button

    • Verify virtualPage fires on popstate
    • Check metadata reverts to previous page
    • Confirm no duplicate tracking
  3. 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:

  • Modal popups (newsletter signups, promotions)
  • Interstitial ads
  • Cookie consent banners

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