Skip to main content

Data sharing

Burst Statistics v3.3.0 includes an anonymous usage data collection system (telemetry) that sends aggregated, non-personal statistics to the Burst Statistics team once per month. This data helps improve the plugin.

No personally identifiable information is collected. All data is anonymised at the point of collection using a one-way site hash.


Overview

The data sharing system is made up of two main components:

ClassResponsibility
Burst\Admin\Data_Sharing\Data_SharingSchedules and triggers the monthly telemetry send
Burst\Admin\Data_Sharing\Data_AggregationCollects, aggregates, and dispatches the payload

Data is collected by individual data collectors (one per category) and combined into a single JSON payload sent via HTTP POST to:

https://api.burst-statistics.com/v2/telemetry
caution

Telemetry is never sent from staging environments. See Staging detection for the full list of detected patterns.


Scheduling

The send is triggered by the burst_monthly WordPress cron action. When that fires, a single-event cron job (burst_telemetry_send) is scheduled with a random offset between 24 and 720 hours. This spreads load across the month rather than all sites sending simultaneously.

The random offset is stored persistently in the burst_telemetry_offset option so the same offset is reused if WordPress cron runs multiple times before the event fires.

Relevant WordPress options:

OptionDescription
burst_telemetry_offsetRandom hour offset (24–720) used to stagger the send time
burst_last_telemetry_sendUnix timestamp of the most recent successful send

Data collected

Each category is collected by a dedicated Data_Collector subclass. The aggregated payload always includes:

FieldTypeDescription
site_hashstringSHA-256 hash of site_url + AUTH_KEY. Identifies the site without transmitting the URL.
is_testbooltrue only during test sends. Test payloads are validated but not stored by the API.
settingsobjectActive plugin settings
goalsobjectConfigured conversion goals
environmentobjectWordPress version, PHP version, and similar environment metadata
email_reportsobjectEmail report configuration (not personal email addresses)
metricsobjectAggregated pageview and event counts for the reporting period

Database metrics

Database table row counts are collected by Burst\Admin\Data_Sharing\Data_Collectors\Metrics\Database_Metrics. The collector uses the Database_Helper trait and validates each table suffix through validate_table_name() before issuing a COUNT(*) query.

Changed in v3.4.0: Database_Metrics now uses validate_table_name() (via the Database_Helper trait) instead of esc_sql() for table-name sanitisation. Suffixes that are not in the trait's allow-list are rejected before the query runs.

Query performance metrics

Slow query telemetry is collected by Burst\Admin\Data_Sharing\Data_Collectors\Metrics\Query_Stats_Metrics. The collector returns up to the top 10 entries from {$wpdb->prefix}burst_query_stats, ordered by avg_execution_time DESC, including a per-row date_range_days value indicating the date span of the originating query.

Returned fields per query row (v3.4.0+):

FieldTypeDescription
sql_querystringThe recorded SQL query
avg_execution_timefloatAverage execution time in seconds
max_execution_timefloatSlowest recorded execution time in seconds
min_execution_timefloatFastest recorded execution time in seconds
execution_countintNumber of times the query has been executed
date_range_daysintDate span (in days) of the query that produced the entry. 0 when no date range is associated

Report logs

Report send statistics are collected by Burst\Admin\Data_Sharing\Data_Collectors\Reports_Data and reported under the email_reports payload. The collector aggregates rows from {$wpdb->prefix}burst_report_logs by queue_id, resolves a single final status per queue, and counts each queue once.

Changed in v3.4.0: Reports_Data has been rewritten to aggregate report logs by queue_id instead of issuing three separate COUNT(*) queries. Each queue is now classified by combining its parent row (batch_id IS NULL) with its child batch rows, and the resolved final status determines whether it counts toward successful_sends, failed_sends, or neither.

Status resolution rules:

  • If a parent status exists and is not PROCESSING, that status wins.
  • Otherwise, if any child batch is PARTLY_SENT, the queue is PARTLY_SENT.
  • Otherwise, if all child batches share a single status, that status wins.
  • Otherwise, the queue is SENDING_FAILED.

Queues whose final status is PROCESSING (or unresolved) are skipped entirely. Queues with queue_id matching test-% are excluded from the aggregation.

A queue is counted as successful only when its final status is SENDING_SUCCESSFUL. The new is_failed_status() helper counts a queue as failed when its final status is one of:

  • SENDING_FAILED
  • EMAIL_DOMAIN_ERROR
  • EMAIL_ADDRESS_ERROR
  • CRON_MISS
  • PARTLY_SENT

The returned payload shape is unchanged:

FieldTypeDescription
reports_sent_last_monthintNumber of distinct queues resolved to a final non-processing status in the reporting window
successful_sendsintQueues whose final status is SENDING_SUCCESSFUL
failed_sendsintQueues whose final status is counted as failed by is_failed_status()

Anonymisation

The site identifier sent in every payload is a one-way SHA-256 hash:

hash( 'sha256', get_site_url() . AUTH_KEY );

The raw site URL is never transmitted. Two sites with the same URL but different AUTH_KEY values produce different hashes.


Kill switch

Before each send, Burst fetches a plain-text control file from:

https://burst.ams3.cdn.digitaloceanspaces.com/feedback/switch.txt

The response body controls whether the send proceeds:

Response bodyBehaviour
enabledSend proceeds normally
disabledSend is skipped for all sites
0100 (integer)Send proceeds for that percentage of sites (random per-site roll)
Any other value / unreachableSend proceeds (fail-open)

If the kill switch disables a send, burst_last_telemetry_send is still updated so the site does not retry on the next cron cycle.


Staging detection

No data is sent when the site is identified as a non-production environment. The check uses wp_get_environment_type() first, then inspects the site URL for the following patterns:

Pattern typeExamples
Environment type not productionstaging, development, local (WP core values)
Localhostlocalhost, localhost/…
Local TLD*.local
Staging subdomain prefixstaging.*, stg.*, test.*, beta.*, acceptance.*
Staging subdomain infix*.dev.*, *.test.*, *.stg.*
Managed staging hosts*.instawp.co

Caching

Aggregated data is cached in the burst_aggregated_data_v2 transient for one week. This reduces database load on repeated API calls. The cache is bypassed when wp_get_environment_type() is not production.

Changed in v3.4.0: The transient key has been bumped from burst_aggregated_data to burst_aggregated_data_v2 to invalidate stale payloads cached under the previous schema. The old transient is no longer read or written — sites upgrading from v3.3.0 will simply rebuild the cache on the next aggregation cycle.

To clear the cache programmatically:

Show code
$aggregation = new \Burst\Admin\Data_Sharing\Data_Aggregation(
strtotime( '-1 month' ),
time()
);
$aggregation->clear_cache();

API request format

Payloads are sent as JSON POST requests with these headers:

HeaderValue
Content-Typeapplication/json
Acceptapplication/json
HTTP_X_BURST_SIGNATUREValue of the BURST_PUBLIC_KEY constant

WordPress cron hooks

burst_monthly

Fires once per month via Burst's internal cron management. The data sharing system uses this action to schedule the deferred telemetry send via Data_Sharing::schedule_telemetry().

This action is not exclusive to data sharing — other subsystems also hook into it.

burst_telemetry_send

Single-event cron hook that performs the actual data collection and API send. Scheduled by Data_Sharing::schedule_telemetry() with a random offset after burst_monthly fires.

Parameters: none.

Example — observing the send:

add_action( 'burst_telemetry_send', function() {
// Executes immediately before Burst sends telemetry data.
}, 1 );
caution

Removing burst_telemetry_send prevents the send only for the currently scheduled event. The next burst_monthly cycle will reschedule it.


Data collector filters

burst_data_sharing_license_status

Filters the license-status value included in the telemetry settings payload.

Default value: free.

burst_data_sharing_subscription_tier

Filters the subscription-tier string included in the telemetry settings payload.

Default value: free.

burst_data_sharing_ecommerce_metrics

Filters the ecommerce metrics payload before it is attached to telemetry.

This hook receives the Ecommerce_Metrics collector instance as the second parameter. Return null to omit ecommerce metrics entirely, or return an array payload to include custom ecommerce telemetry.

Example:

Show code
add_filter( 'burst_data_sharing_ecommerce_metrics', function( $metrics, $collector ) {
if ( ! my_plugin_should_share_ecommerce_metrics() ) {
return null;
}

return $collector->collect();
}, 10, 2 );

Disabling data sharing

Data sharing is suppressed automatically on staging and local environments (see Staging detection).

To disable it unconditionally on a production site, prevent the cron hook from running before Burst's own callback:

Show code
// In a must-use plugin or your theme's functions.php.
add_action( 'burst_telemetry_send', function() {
remove_action(
'burst_telemetry_send',
[ burst()->data_sharing, 'send_monthly_telemetry' ]
);
}, 0 );

To prevent the event from ever being scheduled, intercept burst_monthly early:

Show code
add_action( 'burst_monthly', function() {
remove_action(
'burst_monthly',
[ burst()->data_sharing, 'schedule_telemetry' ]
);
}, 1 );

Test sends

Data_Sharing::send_test_telemetry() dispatches a payload with is_test: true. The API validates the payload structure but does not persist the data. An optional custom endpoint can be passed for integration testing.

Show code
$data_sharing = new \Burst\Admin\Data_Sharing\Data_Sharing();
$result = $data_sharing->send_test_telemetry();

if ( $result['success'] ) {
echo 'Accepted. HTTP ' . $result['status_code'];
} else {
echo 'Failed: ' . $result['message'];
}

Return value:

KeyTypeDescription
successbooltrue if the API returned a 2xx status code
status_codeintHTTP response code
messagestringHuman-readable status from the API response body
dataarray|nullFull parsed JSON response body
endpointstringURL the request was sent to

When a visitor arrives via a share link, Burst assigns the burst_viewer role and grants the view_burst_statistics capability. This allows the visitor to access general statistics pages that were included in the shared link's shared_tabs array.

Allowed capabilities for shared-link visitors (v3.3.0+):

Show code
$allowed_caps = [
'view_burst_statistics',
'burst_viewer',
];

Pro - BusinessAvailable in the Business tier

Ecommerce tab sharing requires Burst Pro. See Revenue & Sales Tracking.

Access to the ecommerce (Sales) tab on a shared link is now controlled exclusively by the ecommerce_tab_is_shared() method on Burst\Admin\Share\Share. It inspects the shared_tabs array stored on the token and returns true only when 'sales' is present.

// Resolved automatically via burst_loader()->admin->share.
$share = burst_loader()->admin->share;
$can_view_sales = $share->ecommerce_tab_is_shared();

Internally, user_can_view_sales() (from the Admin_Helper trait) delegates to this method whenever is_shared_link_request() returns true:

Show code
// From trait-admin-helper.php — simplified illustration.
if ( $this->is_shared_link_request() ) {
return burst_loader()->admin->share->ecommerce_tab_is_shared();
}

Added in v3.3.0. A protected helper available to any class using the Admin_Helper trait. Returns true when the current request carries either of the two share-token identifiers.

Show code
protected function is_shared_link_request(): bool {
return isset( $_SERVER['HTTP_X_BURST_SHARE_TOKEN'] )
|| isset( $_GET['burst_share_token'] );
}

This helper is used as a guard inside user_can_view_sales() and can be called from any class that uses Admin_Helper to branch logic for anonymous shared-link visitors versus authenticated users.