ChangeTrace
Help

For developers

Constants, filters, storage and hooks in the ChangeTrace WordPress plugin.

Reference for the plugin's developer surface. There is no public HTTP API — every endpoint is either a dashboard session or a site token issued to the plugin.

Requires: WordPress 6.2+, PHP 8.1+. Neither is checked at runtime, so an under-spec site fatals rather than degrading.

Constants

Define in wp-config.php before WordPress loads:

define( 'CHANGETRACE_API_BASE_URL', 'https://api.change-trace.com' );
define( 'CHANGETRACE_APP_URL',      'https://app.change-trace.com' );
define( 'CHANGETRACE_PRIVACY_URL',  'https://change-trace.com/privacy-policy' );
define( 'CHANGETRACE_TERMS_URL',    'https://change-trace.com/terms-of-service' );

The first two are what you change to point a staging site at a different environment.

Filters

// Same four values, at runtime.
add_filter( 'changetrace_api_base_url', fn( $url ) => 'https://api.staging.example.com' );
add_filter( 'changetrace_app_url',      fn( $url ) => 'https://app.staging.example.com' );
add_filter( 'changetrace_privacy_url',  fn( $url ) => $url );
add_filter( 'changetrace_terms_url',    fn( $url ) => $url );

// JavaScript error sampling. Default 0.25. Use 1.0 to capture everything while debugging —
// it is a real bandwidth cost on a busy site, so put it back afterwards.
add_filter( 'changetrace_js_error_sample_rate', fn( $rate ) => 1.0 );

Scheduled jobs

HookScheduleDoes
changetrace_heartbeatHourlyPosts a heartbeat
changetrace_flush_queueEvery 5 minutes (custom schedule changetrace_flush_interval)Sends up to 50 queued events

Both re-schedule themselves on init when a token is present, so a lost cron entry heals itself. Deactivation clears both.

Storage

No custom tables. Twelve options, none autoloaded:

OptionHolds
changetrace_site_tokenThe site token
changetrace_connected_hostDomain pin for clone detection
changetrace_consentConsent record: agreed, version, time, user, site URL
changetrace_connect_pendingIn-flight handshake state
changetrace_connect_last_errorLast connect failure
changetrace_last_heartbeatTime, ok, HTTP code
changetrace_installed_atActivation timestamp
changetrace_event_queuePending events
changetrace_flush_backoffRetry state
changetrace_remote_configCached configuration, 1-hour TTL
changetrace_snapshot_sent_atSnapshot timestamp
changetrace_state_baselineLast-seen plugins, theme, WP and PHP versions

Plus two transients (changetrace_activation_redirect, changetrace_reconcile_lock) and one user meta key for notice dismissal. Uninstall removes all of it.

Queue behaviour

  • Bounded FIFO, 500 events; when full the oldest are dropped.
  • Flush every 5 minutes, 50 per batch.
  • Events are removed only after the API returns 2xx.
  • On failure: exponential backoff from 60s, doubling, capped at 3600s, 8 attempts.
  • No-op when disconnected or in safe mode.

Event types

The underscore forms are the real contract.

Changes: plugin_activated, plugin_deactivated, plugin_updated, plugin_removed, theme_changed, core_updated, php_version_changed

Errors: php_error, php_fatal, js_error, http_5xx, http_timeout, rest_5xx

WooCommerce: order_created, order_failed, payment_failed, checkout_started, checkout_failed, refund

EDD: edd_order_created, edd_order_failed, edd_payment_failed, edd_refund

Every event is wrapped in an envelope carrying a client-generated event_id (UUID v4), the site URL, type, ISO-8601 UTC timestamp, source, schema version and severity. Ingestion deduplicates on event_id, so re-sending a batch is safe.

Declared but not implemented

The remote configuration lists option_changes and user_role_changes modules, but no collector emits them — do not build on those. Likewise heartbeat_interval_seconds is present in the config and read, but nothing consumes it; the heartbeat is WordPress's hourly schedule.

REST route

One, and it is for the plugin's own bundled script:

POST /wp-json/changetrace/v1/client-error

Requires a valid X-WP-Nonce. Always returns 204, including on error — it never 500s, and it drops silently when the site is not connected. Bodies over 16 KB are replaced with a stub.

Outbound endpoints

EndpointPurpose
POST /api/v1/connect/exchangeSwap a one-time code for a token (the only unauthenticated call)
GET /api/v1/sites/me/statusVerify a token
GET /api/v1/sites/me/configFetch remote configuration
POST /api/v1/sites/me/snapshotSend the baseline snapshot
POST /api/v1/sites/heartbeatHourly heartbeat
POST /api/v1/sites/events/batchSend queued events

All carry Authorization: Bearer site_tok_… except the exchange, plus an X-ChangeTrace-Site-Url header the API validates — a mismatch is a 409, which is what enforces safe mode server-side.

Debugging

There is no debug toggle and no log file. Connect failures are written with error_log() only when WP_DEBUG is true. The status table and the Send test heartbeat button are the plugin's entire diagnostic surface.

Sanitization

Applied on your server, before sending. Recursive key-based redaction across ~40 sensitive key substrings, regex redaction of email addresses and 12+ digit runs inside strings, query string and fragment stripped from URLs, absolute paths rewritten relative to the WordPress install, 8 KB payload cap and 2,048-character string cap with truncation markers.

On this page