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
| Hook | Schedule | Does |
|---|---|---|
changetrace_heartbeat | Hourly | Posts a heartbeat |
changetrace_flush_queue | Every 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:
| Option | Holds |
|---|---|
changetrace_site_token | The site token |
changetrace_connected_host | Domain pin for clone detection |
changetrace_consent | Consent record: agreed, version, time, user, site URL |
changetrace_connect_pending | In-flight handshake state |
changetrace_connect_last_error | Last connect failure |
changetrace_last_heartbeat | Time, ok, HTTP code |
changetrace_installed_at | Activation timestamp |
changetrace_event_queue | Pending events |
changetrace_flush_backoff | Retry state |
changetrace_remote_config | Cached configuration, 1-hour TTL |
changetrace_snapshot_sent_at | Snapshot timestamp |
changetrace_state_baseline | Last-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-errorRequires 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
| Endpoint | Purpose |
|---|---|
POST /api/v1/connect/exchange | Swap a one-time code for a token (the only unauthenticated call) |
GET /api/v1/sites/me/status | Verify a token |
GET /api/v1/sites/me/config | Fetch remote configuration |
POST /api/v1/sites/me/snapshot | Send the baseline snapshot |
POST /api/v1/sites/heartbeat | Hourly heartbeat |
POST /api/v1/sites/events/batch | Send 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.

