Tracking
Burst Statistics tracks visitors and sessions by collecting page hits from the browser via JavaScript and processing them server-side. This document covers the full tracking lifecycle, session handling, IP processing, and all available extension points.
Overview
When a visitor loads a page, Burst enqueues a JavaScript tracking script (burst.min.js or burst-cookieless.min.js). The script sends a JSON payload to the server on each page view or time-on-page update. The server validates, sanitizes, and stores the hit in the burst_statistics and burst_sessions database tables.
Each hit is classified as either a create (new row in burst_statistics) or an update (accumulate time_on_page on an existing row). Session records in burst_sessions are created on the first hit and updated on subsequent hits within the same session window (30 minutes).
Tracking Endpoints
Burst supports two server-side collection mechanisms depending on the site's server configuration.
REST API Endpoint (default)
POST /wp-json/burst/v1/track
Content-Type: application/json
The request body must be a JSON-encoded string (double-encoded). The endpoint is registered via rest_api_init and has permission_callback set to __return_true (no authentication required). A { "request": "test" } body returns { "success": "test" } without recording a hit.
Beacon Endpoint (fallback)
When the REST API is unavailable, Burst falls back to a beacon endpoint that reads raw php://input. A body of request=test returns HTTP 200 without recording a hit.
The active endpoint is determined by Endpoint::get_tracking_status(). When the beacon endpoint is active, the tracking script does not depend on wp-api-fetch.
Hit Payload
The JavaScript client sends the following fields in the JSON payload:
| Field | Type | Description |
|---|---|---|
url | string | Full URL of the current page, including query string |
referrer_url | string | HTTP referrer URL |
user_agent | string | Browser user-agent string |
uid | string | Cookie-based unique visitor identifier |
fingerprint | string | Canvas/audio fingerprint (cookieless mode) |
time_on_page | int | Seconds spent on the current page |
completed_goals | array | Array of goal IDs completed client-side |
page_id | int | WordPress post/term ID |
page_type | string | Page type identifier (see Page Type Identifiers) |
should_load_ecommerce | bool | Whether to load ecommerce tracking |
Tracking Lifecycle
As of v3.3.0 the IP-block check is performed at the very top of track_hit(), before prepare_tracking_data() runs. Previously the check was performed separately in each endpoint handler. Any custom code that relied on the check occurring after sanitization must be updated.
Browser sends POST payload
│
▼
IP block check ──blocked──► return 'ip blocked'
│
▼
prepare_tracking_data() — sanitize & parse user-agent
│
▼
Custom block rules check ──blocked──► return 'blocked by custom rule'
│
▼
Referrer spam check ──spam──► return 'referrer is spam'
│
▼
get_hit_type() — determine 'create' or 'update', fetch last row
│
▼
burst_before_track_hit filter
│
▼
Session: create or update burst_sessions
│
├── hit_type = 'update' & same URL ──► update_statistic() (accumulate time_on_page)
│
└── hit_type = 'create' ──► burst_before_create_statistic action
► create_statistic()
► burst_after_create_statistic action
│
▼
create_goal_statistic() (if goals completed)
Hit Type Determination
A hit is classified as an update when all of browser_id, browser_version_id, platform_id, and device_id are 0 — meaning the client did not send a user-agent (typically a heartbeat ping). The last matching row is fetched from burst_statistics for the same uid/fingerprint within the last 30 minutes.
| Condition | Result |
|---|---|
| Last row found, all device IDs = 0 | update — accumulate time_on_page |
| Last row found, device IDs present | create — new pageview row |
| No last row found, all device IDs = 0 | Error — returns empty (no action) |
| No last row found, device IDs present | create — first visit |
Session Handling
Sessions are stored in {prefix}_burst_sessions. A new session is created when no prior hit exists for the visitor within the last 30 minutes.
Session Table Schema
| Column | Type | Description |
|---|---|---|
ID | int | Auto-increment primary key |
first_visited_url | TEXT | First page URL in the session |
last_visited_url | TEXT | Most recently visited URL |
host | varchar(255) | Site host (populated when domain filtering is enabled) |
referrer | varchar(255) | Traffic source referrer |
goal_id | int | Associated goal ID |
city_code | int | GeoIP city lookup ID |
browser_id | int | Browser lookup table ID |
browser_version_id | int | Browser version lookup ID |
platform_id | int | OS/platform lookup ID |
device_id | int | Device type lookup ID |
first_time_visit | tinyint | 1 if this is the visitor's first visit |
bounce | tinyint | 1 if the session is still a bounce (single page) |
Changed in v3.3.0: browser_id, browser_version_id, platform_id, device_id, first_time_visit, and bounce were moved from burst_statistics to burst_sessions. All session-level fields are now written exclusively to this table. See the Database Tables section for details.
A session is updated when the visitor navigates to a different URL within the same session window. The last_visited_url and bounce flag are updated accordingly.
Multi-Domain Tracking
Burst detects multi-domain setups automatically. The first domain seen is stored in burst_first_domain. If a subsequent hit comes from a different hostname, burst_is_multi_domain is set to true. When the Filter by domain setting is enabled, the host column is populated on sessions to allow per-domain filtering.
IP Processing
IP address resolution is handled by Burst\Frontend\Ip\Ip. The following headers are checked in priority order:
| Priority | Header | Source |
|---|---|---|
| 1 | HTTP_CF_CONNECTING_IP | Cloudflare real client IP |
| 2 | HTTP_TRUE_CLIENT_IP | Akamai / Cloudflare Enterprise |
| 3 | HTTP_X_FORWARDED_FOR | Reverse proxies (leftmost public IP used) |
| 4 | HTTP_X_REAL_IP | nginx proxy |
| 5 | HTTP_X_CLUSTER_CLIENT_IP | Cluster environments |
| 6 | HTTP_CLIENT_IP | General proxy header |
| 7 | REMOTE_ADDR | Direct connection |
Public (non-private, non-reserved) IPs are preferred. Both IPv4 and IPv6 are supported, including IPv4-mapped IPv6 addresses.
IP Blocklist
IPs are blocked before any data is recorded. The blocklist is configured under Settings → IP Blocklist (ip_blocklist option) as one IP or CIDR range per line. Both IPv4 and IPv6 CIDR notation is supported (e.g., 192.168.1.0/24, 2001:db8::/32).
Test Hit Bypass
A request can bypass the IP block only when the URL contains burst_test_hit and includes a valid nonce query parameter that matches the transient stored under burst_onboarding_token.
Changed in v3.3.0: Previously, any URL containing burst_test_hit bypassed the IP block. A nonce check is now required to prevent unauthorized bypass of the IP block.
# URL that will bypass the IP block check (nonce must match burst_onboarding_token transient)
https://example.com/?burst_test_hit=1&nonce=<valid-nonce>
Visitor Identification
Burst supports two visitor identification modes:
Cookie-based (default)
A uid value is generated by the JavaScript client and stored in a browser cookie. The cookie retention period defaults to 30 days and is configurable via the burst_cookie_retention_days filter.
Cookieless (fingerprint)
When cookieless tracking is enabled, a browser fingerprint (canvas/audio based) is used as the uid. The fingerprint is also stored in a PHP session ($_SESSION['burst_fingerprint']) when server-side goals or ecommerce tracking is active.
PHP sessions for fingerprint storage fall back to the WordPress uploads directory (wp-content/uploads/burst-sessions/) if the default session save path is not writable.
User-Agent Parsing
Browser, browser version, platform, and device type are extracted from the User-Agent header by Burst\UserAgentParser\UserAgentParser. The results are stored in normalised lookup tables:
{prefix}_burst_browsers{prefix}_burst_browser_versions{prefix}_burst_platforms{prefix}_burst_devices
Lookup table IDs are cached in memory per request and via the WordPress object cache (burst cache group, key burst_{item}_all).
Page Type Identifiers
The page_type field is inserted into the <body> tag as a data-burst_type attribute by PHP output buffering. Valid values are:
| Value | Description |
|---|---|
front-page | Static homepage |
blog-index | Posts homepage |
date-archive | Date archive |
404 | Not found page |
archive-generic | Generic archive |
wc-shop | WooCommerce shop page |
tag | Tag archive |
tax | Custom taxonomy archive |
author | Author archive |
search | Search results |
category | Category archive |
| Any public post type slug | e.g., post, page, product |
The page ID (data-burst_id) and type are injected into the opening <body> tag using output buffering (ob_start/ob_end_flush).
Custom Block Rules
Settings → Custom Block Rules (custom_block_rules option) accepts one rule per line. Rules are matched against the page URL, referrer, and user-agent. Rules can be plain strings (case-insensitive substring match) or regex patterns.
Regex pattern format: Must start and end with / and may include flags (i, m, s, x, u).
# Plain string — blocks any URL, referrer, or UA containing this text
example-spam.com
# Regex — blocks URLs ending in a number
/text-in-url[0-9]+/i
# Regex — blocks all traffic from a specific domain
/^https:\/\/domain\./
# Regex — blocks known bots
/facebook(bot|crawler)/i
Invalid regex patterns are logged and skipped. Block rule matches are logged when BURST_DEBUG is defined and truthy.
Excluding Visitors from Tracking
By User Role
Logged-in users can be excluded by role via Settings → User Role Blocklist (user_role_blocklist option). The roles list is filterable:
burst_roles_excluded_from_tracking
Filter the list of WordPress user roles that are excluded from tracking.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$roles | array | Array of role slugs excluded from tracking |
Example:
add_filter( 'burst_roles_excluded_from_tracking', function( $roles ) {
$roles[] = 'editor';
return $roles;
} );
By Headless Mode
Setting headless to true in Burst options (or defining the constant BURST_HEADLESS) prevents the tracking script from being enqueued entirely.
Page Builder Previews
Tracking is automatically suppressed when the current request is detected as a page builder or plugin preview.
Hooks and Filters Reference
burst_before_track_hit
Fires after data sanitization but before session handling and database writes. Use this hook to modify tracking data or abort tracking by returning modified data.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$sanitized_data | array | Sanitized tracking data (see payload fields) |
$hit_type | string | 'create' or 'update' |
$previous_hit | array | Previous hit row from burst_statistics, or empty array |
Example:
add_filter( 'burst_before_track_hit', function( $data, $hit_type, $previous_hit ) {
// Add a custom field or modify the data before it is stored.
return $data;
}, 10, 3 );
burst_before_create_statistic
Fires immediately before a new row is inserted into burst_statistics.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$statistic | array | Statistic data about to be inserted |
Changed in v3.3.0: $statistic no longer contains session-level fields (bounce, browser_id, browser_version_id, platform_id, device_id, referrer, city_code, first_time_visit). These fields are now written exclusively to burst_sessions before this action fires.
Example:
add_action( 'burst_before_create_statistic', function( $statistic ) {
// Inspect or log data before insert.
// Note: session-level fields (browser_id, device_id, etc.) are not present in $statistic.
} );
burst_after_create_statistic
Fires immediately after a new row is inserted into burst_statistics.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$insert_id | int | ID of the newly created statistic row |
$statistic | array | Statistic data that was inserted |
Changed in v3.3.0: $statistic no longer contains session-level fields (bounce, browser_id, browser_version_id, platform_id, device_id, referrer, city_code, first_time_visit). These fields are now written exclusively to burst_sessions.
Example:
add_action( 'burst_after_create_statistic', function( $insert_id, $statistic ) {
// React to a new pageview being recorded.
// Note: session-level fields (browser_id, device_id, etc.) are not present in $statistic.
}, 10, 2 );
burst_tracking_options
Filter the options array that is passed to the frontend JavaScript via wp_localize_script. The returned value populates the global burst JavaScript object.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$options | array | Full tracking options array |
Example:
add_filter( 'burst_tracking_options', function( $options ) {
// Disable debug mode regardless of BURST_DEBUG constant.
$options['options']['debug'] = 0;
return $options;
} );
The $options array structure:
[
'tracking' => [
'isInitialHit' => true,
'lastUpdateTimestamp' => 0,
'beacon_url' => 'https://example.com/path/to/beacon',
'ajaxUrl' => 'https://example.com/wp-admin/admin-ajax.php',
],
'options' => [
'cookieless' => 0, // 1 = cookieless mode
'pageUrl' => 'https://…',
'beacon_enabled' => 0, // 1 = use beacon endpoint
'do_not_track' => 0, // 1 = respect DNT header
'enable_turbo_mode' => 0, // 1 = defer script
'track_url_change' => 0, // 1 = track SPA navigation
'cookie_retention_days' => 30,
'debug' => 0,
],
'goals' => [
'completed' => [],
'scriptUrl' => 'https://…/burst-goals.js?v=…',
'active' => [],
],
'cache' => [
'uid' => null,
'fingerprint' => null,
'isUserAgent' => null,
'isDoNotTrack' => null,
'useCookies' => null,
],
]
burst_cookie_retention_days
Filter the number of days the visitor UID cookie is retained.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$days | int | Cookie lifetime in days (default: 30) |
Example:
add_filter( 'burst_cookie_retention_days', function( $days ) {
return 365; // Keep cookie for one year.
} );
burst_goals_script_url
Filter the URL of the goals JavaScript bundle.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$url | string | Full URL to burst-goals.js |
Example:
add_filter( 'burst_goals_script_url', function( $url ) {
return 'https://cdn.example.com/burst-goals.js';
} );
burst_script_dependencies
Filter the script handles that the main tracking script depends on.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$deps | array | Array of registered script handles |
Example:
add_filter( 'burst_script_dependencies', function( $deps ) {
$deps[] = 'my-custom-script';
return $deps;
} );
burst_ip_blocklist
Filter the array of blocked IP addresses and CIDR ranges before the current visitor's IP is checked.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$blocked_ips | array | Array of IP addresses and CIDR ranges |
Example:
add_filter( 'burst_ip_blocklist', function( $blocked_ips ) {
$blocked_ips[] = '203.0.113.0/24'; // Block an additional CIDR range.
return $blocked_ips;
} );
burst_visitor_ip
Filter the resolved visitor IP address before it is used for blocklist checks.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$ip | string | Resolved IP address (may be empty string if unresolvable) |
Example:
add_filter( 'burst_visitor_ip', function( $ip ) {
// Override IP resolution for requests behind a trusted proxy.
if ( ! empty( $_SERVER['HTTP_X_CUSTOM_IP'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_CUSTOM_IP'] ) );
}
return $ip;
} );
burst_obfuscate_filename
Filter whether ghost mode (filename obfuscation) is active. When truthy, all tracking assets are renamed with a b- prefix and served from the uploads directory.
Parameters:
| Parameter | Type | Description |
|---|---|---|
$enabled | bool | Whether ghost mode is active |
Example:
add_filter( 'burst_obfuscate_filename', '__return_true' );
Settings Reference
The following plugin settings directly affect tracking behaviour:
| Option key | Type | Description |
|---|---|---|
enable_cookieless_tracking | bool | Use fingerprinting instead of cookies |
enable_do_not_track | bool | Honour the browser's DNT: 1 header |
enable_turbo_mode | bool | Load tracking script with defer (in footer) instead of async |
track_url_change | bool | Track SPA URL changes (query string included in URL comparison) |
filtering_by_domain | bool | Store and filter by host in sessions |
custom_block_rules | string | Newline-separated block rules (strings or regex) |
ip_blocklist | string | Newline-separated IP addresses or CIDR ranges |
user_role_blocklist | array | WordPress user roles excluded from tracking |
ghost_mode | bool | Obfuscate tracking asset filenames |
headless | bool | Disable script enqueueing (API-only mode) |
combine_vars_and_script | bool | Inline tracking options into a single pre-built JS file |
Debug Mode
Define BURST_DEBUG as true in wp-config.php to enable verbose logging:
define( 'BURST_DEBUG', true );
When debug mode is active:
- Custom block rule matches are logged.
- Failed statistic inserts/updates are logged.
- Client-side HTTP 400 tracking errors are accepted via the
burst_tracking_errorAJAX action and written to the error log. - The
debug: 1flag is included in the JavaScriptburstoptions object.
Database Tables
| Table | Purpose |
|---|---|
{prefix}_burst_statistics | One row per pageview. Foreign key to burst_sessions. |
{prefix}_burst_sessions | One row per visitor session. |
{prefix}_burst_goal_statistics | Many-to-many join between statistics rows and completed goals. |
{prefix}_burst_browsers | Browser name lookup table. |
{prefix}_burst_browser_versions | Browser version lookup table. |
{prefix}_burst_platforms | Operating system lookup table. |
{prefix}_burst_devices | Device type lookup table. |
Lookup tables are populated automatically on the first hit containing a new value. IDs are cached in the WordPress object cache under the burst group.
As of v3.3.0 the columns browser_id, browser_version_id, platform_id, device_id, first_time_visit, and bounce have been removed from burst_statistics and are stored exclusively in burst_sessions. A background database migration (move_columns_to_sessions) runs automatically on upgrade. Any custom SQL queries or third-party integrations that read these columns directly from burst_statistics must be updated to join burst_sessions instead.