Who Changed It/Docs/Guides
Get told the moment something changes on your WordPress site — by email, in a Slack or Discord channel, or from a Telegram bot.
Four channels ship in the plugin: email, Slack, Discord and Telegram. They share one set of rules — the same severity threshold, the same quiet period, the same grouping — so you configure the behaviour once and then choose where it goes. Everything below is on the plugin's settings screen; no code is required for any of it.
These sit above the individual channels, and they are the ones worth getting right before you switch anything on.
Default: dangerous only. The choice is dangerous, dangerous and strange, or everything. The default exists because a dangerous event is by definition one you want to know about now, while everything below it is something you can read in the log when you next look. Moving the threshold down to everything on a busy site will page you about routine page saves — useful for a staging site you are auditing, painful on production. How events earn their severity is covered in how events are classified.
Default: 15 minutes. At most one message per event type per channel per window, adjustable from 1 minute to 24 hours. This matters more than it sounds: a brute-force attack can generate hundreds of dangerous login_failed events in a minute, and without a quiet period the one notification you needed would be buried under the noise of the attack itself. With it you get one message saying it is happening, and the log keeps every individual attempt for when you investigate.
The quiet period is per event type, not global. A burst of failed logins will not silence a notification about a new administrator account being created a minute later — those are different event types with separate windows.
Notifications are queued while the request runs and sent once it ends, so several events caused by one action arrive as a single message rather than a pile of them, and no admin screen ever waits on a remote server to respond. One event reads as a full record; several read as a summary list:
3 flagged events on Example Site
• [DANGEROUS] user_created — t.okafor (j.alvarez)
• [DANGEROUS] user_role_changed — t.okafor (j.alvarez)
• [STRANGE] login_failed — admin ((none))
https://example.com/wp-admin/admin.php?page=whochita-logOn by default, to your site's admin address. It is a checkbox on the settings screen, so you can switch it off and keep only chat notifications, or the other way round.
The message is deliberately plain text and short enough to read on a phone lock screen:
Subject: [Your Site] Dangerous activity detected: user_created
Who Changed It? flagged a dangerous event on your site.
Event: user_created
Object: t.okafor
User: j.alvarez (ID 4)
IP: 77.12.203.9
Time: 2026-08-04 09:52:11 UTC
Why it was flagged:
- A new administrator account was created.
Full log: https://example.com/wp-admin/admin.php?page=whochita-logLeave the field empty and alerts go to your WordPress admin address. Fill it in with a comma-separated list to send to several people, a shared ops mailbox or a ticketing address — anything that is not a valid email address is ignored rather than silently breaking the send. The list can also be replaced in code with the whochita_alert_recipient filter, which receives the array the settings screen produced.
Default: who-changed-it@yourdomain.com, derived from your site's domain. Switch to custom to set your own From name and address. Change this if your host, SPF/DKIM policy or SMTP plugin rejects mail from addresses that do not exist as real mailboxes — a wrong From address is the most common reason alerts silently vanish.
Only hooks.slack.com over HTTPS is accepted — see the security model below for why.
Accepted hosts are discord.com and discordapp.com, HTTPS only. Discord rejects any message over 2,000 characters outright, so messages are trimmed before sending — a very large grouped notification will end mid-list, and the log link is the authority.
Telegram needs two values rather than a URL, because there is no webhook to copy:
Messages go to api.telegram.org, are trimmed at 4,000 characters, and are sent with link previews disabled so a notification does not expand into a preview card of your own wp-admin.
This is the only part of the plugin that talks to the network at all, so it is worth stating exactly what it does and does not do.
For a destination that is not one of the four — Microsoft Teams, Mattermost, PagerDuty, Zapier, n8n, a webhook on your own server, or an external SIEM — whochita_event_logged fires after every event is written and hands you the whole row:
add_action( 'whochita_event_logged', function ( $row, $reasons ) {
if ( 'dangerous' !== $row['severity'] ) {
return; // Filter, or you will forward every routine page save.
}
wp_remote_post( 'https://example.com/your-webhook', array(
'body' => wp_json_encode( array(
'event' => $row['event_type'],
'user' => $row['user_login'] ?: 'no user',
'ip' => $row['ip'] ?: 'no IP',
'why' => $reasons,
) ),
'headers' => array( 'Content-Type' => 'application/json' ),
'timeout' => 5,
'blocking' => false,
) );
}, 10, 2 );Two things to copy from that snippet. Filter on severity, and set 'blocking' => false so a slow webhook never delays the admin request that triggered it. Note also that this hook fires on every event: the threshold and quiet period above belong to the built-in channels, so do your own throttling here if you forward anything below dangerous.
A tidier route for a channel you intend to keep is to register it properly with whochita_notification_channels, supplying its endpoint and body with whochita_notification_endpoint and whochita_notification_body. A channel registered that way inherits the threshold, the quiet period and the grouping instead of reimplementing all three. The full signatures are in hooks and filters.
There are still no scheduled or digest emails: notifications are immediate, and the quiet period bounds their frequency rather than batching them onto a schedule. If you want a weekly summary, the honest answer remains a small cron job of your own reading the log table, or a filtered export.