Who Changed It/Docs/Reference
The complete extension surface — one action and eight filters, with signatures and working examples.
Version 0.8.0 exposes one action and fourteen filters, plus one configuration constant. Together they let you forward events anywhere, log your own events, reclassify anything, and adapt the plugin to your hosting setup. Put the snippets below in a small custom plugin, or in your theme's functions.php if you must.
Nine of those filters are documented on this page. The remaining five — whochita_notification_channels, whochita_notification_endpoint, whochita_notification_body, whochita_notification_request and whochita_notification_throttle — exist to register a notification channel of your own, and are covered in notifications and alerts.
Fires immediately after an event has been written to the log. This is the main integration point — alerting, forwarding to Slack or a SIEM, or triggering your own automation.
do_action( 'whochita_event_logged', array $row, array $reasons );$row contains the inserted record, including its classification and chain position: id, chain_index, event_time (UTC), user_id, user_login, event_type, family, object_type, object_name, severity, ip, reasons, context, and the prev_hash / payload_hash / record_hash triple.
$reasons is the same escalation list as a decoded PHP array, which is what you normally want. Note this action fires for every event, so filter on severity before doing anything expensive. There is a full webhook example in notifications and alerts.
The event-type → severity table, before any rules or heuristics run. Use it to reclassify events for your site, or to register severities for custom events of your own.
add_filter( 'whochita_base_severity_map', function ( $map ) {
// Plugin activations matter more on this site than the default assumes.
$map['plugin_activated'] = 'strange';
// Register a severity for an event we log ourselves.
$map['invoice_voided'] = 'dangerous';
return $map;
} );Valid values are 'normal', 'strange' and 'dangerous'. Anything not in the map is treated as normal. This filter also drives the event list on the settings screen, so custom events added here become mutable there.
The family a single event type belongs to. Use it to file your own events under a real family so per-family retention and the family filter apply to them, instead of leaving them in other.
add_filter( 'whochita_event_family', function ( $family, $event_type ) {
if ( 'invoice_voided' === $event_type ) {
return 'commerce';
}
return $family;
}, 10, 2 );The full family → event-types map, if you would rather reassign in bulk than one event at a time.
The retention window in days for one family, after the settings screen has computed it. Returning 0 means keep everything for that family.
add_filter( 'whochita_family_retention_days', function ( $days, $family ) {
if ( 'commerce' === $family ) {
return 0; // Order history is a financial record; never prune it.
}
return $days;
}, 10, 2 );The global retention window in days, used for any family that has not overridden it. Returning 0 means keep everything.
add_filter( 'whochita_retention_days', function ( $days ) {
return 365; // Override the UI setting; keep one year.
} );Useful for pinning retention in code so it cannot be shortened from the dashboard — on a site where the log is a compliance artefact, that is a meaningful control.
The list of option names whose changes raise an option_changed event. Extend it to audit settings the plugin does not know about — typically your own plugin's options, or a third-party plugin's security-relevant settings.
add_filter( 'whochita_watched_options', function ( $options ) {
$options[] = 'my_plugin_api_endpoint';
$options[] = 'my_plugin_debug_mode';
return $options;
} );Be selective. Watching a high-churn option (a cache timestamp, a counter) will fill the log with noise.
The resolved client IP, before the privacy mode is applied. By default the plugin trusts only REMOTE_ADDR, because X-Forwarded-For is trivially spoofable by the client on a site that is not actually behind a proxy — trusting it blindly would let an attacker forge the IP on their own log entries.
If your site is behind Cloudflare, a load balancer or another reverse proxy, map the header your proxy sets:
add_filter( 'whochita_client_ip', function ( $ip ) {
// Cloudflare. Only do this if traffic really does come through Cloudflare.
if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
}
return $ip;
} );The returned value is validated as an IP address; anything invalid is stored as empty. To stop storing addresses entirely, prefer the privacy mode on the settings screen over returning an empty string here.
Where dangerous-event alerts are emailed. Defaults to your WordPress admin email.
add_filter( 'whochita_alert_recipient', function ( $to ) {
return 'security@example.com';
} );The minimum seconds between alert emails for the same event type. Defaults to 15 minutes.
add_filter( 'whochita_alert_throttle', function ( $seconds ) {
return 5 * MINUTE_IN_SECONDS;
} );Moves the tamper-detection key out of the database. Set it in wp-config.php before accumulating records you care about — changing it later invalidates the existing chain.
define( 'WHOCHITA_CHAIN_KEY', 'a-long-random-string-kept-out-of-the-database' );Why it matters is covered in integrity and evidence.
Custom events go through the logger directly. Pair this with whochita_base_severity_map and whochita_event_family so your event has a severity and a family, and it will then behave exactly like a built-in one: filterable, exportable, alertable, mutable and covered by the hash chain.
WHOCHITA_Logger::log( 'invoice_voided', array(
'object_type' => 'invoice',
'object_name' => 'INV-2026-0481',
'context' => array(
'old_value' => 'issued',
'new_value' => 'void',
'amount' => 129.00,
),
) );The acting user defaults to the current user and the IP is resolved for you. Guard the call with class_exists( 'WHOCHITA_Logger' ) so your code does not fatal if the plugin is deactivated.