- Why we need Alert System instead of just logs
- Alert system architecture based on hooks
- Module 1: Discord Webhook Integration
- Module 2: Email Alert with HTML template
- Module 3: Slack Integration
- Module 4: Telegram Bot Alert
- Alert Router: Deciding which alert to send, when
- Rate Limiting for Alerts: Avoid notification spam
- Configuring webhook URLs via Settings API
- Testing Alert System: Send test notifications
- Conclusion
This article builds a complete Alert & Notification system, integrating Discord, Email, Slack and Telegram, allowing admins to receive instant alerts when dangerous behavior occurs without having to sit in front of the dashboard all day.
Why we need Alert System instead of just logs
Logs are just static data. Alerts are proactive responses.
- Admins can’t sit refreshing the dashboard 24/7.
- Brute force attacks happen in minutes, not hours.
- Some sensitive endpoints (delete-admin, bulk-delete) need immediate alerts.
- Pattern detection is only meaningful when someone acts on it.
The alert system transforms Init Sentinel from a recording tool into an active defense mechanism.
Alert system architecture based on hooks
Init Sentinel already prepared the init_html_security_event hook right in the core function. This is the ideal point to implement alerts without bloating the original function.
// In the core function init_html_log_security_event
do_action( 'init_html_security_event', $log_id, [
'user_id' => $user_id,
'ip_address' => $ip,
'endpoint' => (string) $endpoint,
'action' => (string) $action,
'status_code' => (int) $status_code,
'user_agent' => $user_agent,
'created_at' => gmdate('Y-m-d H:i:s'),
] );
The alert system will hook here and decide whether to send notifications based on severity level, frequency and blacklist/whitelist.
Module 1: Discord Webhook Integration
Discord is a popular choice because it’s free, quick to setup and supports rich embeds with color-coded severity levels.
/**
* Send alert via Discord webhook
* @param array $data Log data from init_html_security_event
*/
function init_html_alert_discord( $data ) {
$webhook_url = apply_filters('init_html_discord_webhook_url', '');
if ( empty($webhook_url) ) {
return;
}
// Determine color by severity
$color = 15158332; // red (default)
if ( $data['status_code'] < 500 ) { $color = 16776960; // yellow (4xx) } $embed = [ 'embeds' => [
[
'title' => '🚨 Security Event Detected',
'description' => sprintf(
"**Endpoint:** `%s`\n**Action:** `%s`\n**IP:** `%s`\n**Status:** `%d`",
$data['endpoint'],
$data['action'],
$data['ip_address'],
$data['status_code']
),
'color' => $color,
'timestamp' => gmdate('c'),
'footer' => [
'text' => sprintf('User ID: %s | UA: %s',
$data['user_id'] ?: 'Guest',
substr($data['user_agent'], 0, 50)
)
]
]
]
];
wp_remote_post( $webhook_url, [
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode($embed),
'timeout' => 5,
'blocking' => false, // async to avoid blocking request
] );
}
Module 2: Email Alert with HTML template
Email is suitable for alerts that don’t need immediate action but require an audit trail.
/**
* Send email alert with beautiful HTML template
* @param array $data Log data
*/
function init_html_alert_email( $data ) {
$to = apply_filters('init_html_alert_email_recipients', get_option('admin_email'));
if ( empty($to) ) {
return;
}
$subject = sprintf(
'[%s] Security Alert: %s',
get_bloginfo('name'),
$data['action']
);
$user = $data['user_id'] ? get_userdata($data['user_id']) : null;
$username = $user ? $user->user_login : 'Guest';
// HTML template
$message = sprintf('
<div style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: #dc3545; color: white; padding: 20px; border-radius: 8px 8px 0 0;">
<h2 style="margin: 0;">🚨 Security Event Detected</h2>
</div>
<div style="background: #f8f9fa; padding: 20px; border: 1px solid #dee2e6;">
<table style="width: 100%%; border-collapse: collapse;">
<tr>
<td style="padding: 8px; font-weight: bold; width: 120px;">Endpoint:</td>
<td style="padding: 8px; font-family: monospace;">%s</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Action:</td>
<td style="padding: 8px; font-family: monospace;">%s</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Status Code:</td>
<td style="padding: 8px;"><span style="background: #dc3545; color: white; padding: 4px 8px; border-radius: 4px;">%d</span></td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">IP Address:</td>
<td style="padding: 8px; font-family: monospace;">%s</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">User:</td>
<td style="padding: 8px;">%s (ID: %d)</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Time:</td>
<td style="padding: 8px;">%s UTC</td>
</tr>
</table>
<div style="margin-top: 20px; padding: 12px; background: white; border-left: 4px solid #0d6efd;">
<strong>User Agent:</strong><br>
<code style="font-size: 12px; word-break: break-all;">%s</code>
</div>
<div style="margin-top: 20px; text-align: center;">
<a href="%s" style="display: inline-block; padding: 12px 24px; background: #0d6efd; color: white; text-decoration: none; border-radius: 4px;">View All Logs</a>
</div>
</div>
</div>
',
esc_html($data['endpoint']),
esc_html($data['action']),
(int) $data['status_code'],
esc_html($data['ip_address']),
esc_html($username),
(int) $data['user_id'],
esc_html($data['created_at']),
esc_html($data['user_agent']),
admin_url('admin.php?page=init-html-security-logs')
);
$headers = ['Content-Type: text/html; charset=UTF-8'];
wp_mail( $to, $subject, $message, $headers );
}
Module 3: Slack Integration
Slack is suitable for teams, allowing discussion right under the alert.
/**
* Send alert via Slack webhook
* @param array $data Log data
*/
function init_html_alert_slack( $data ) {
$webhook_url = apply_filters('init_html_slack_webhook_url', '');
if ( empty($webhook_url) ) {
return;
}
// Color by severity
$color = ($data['status_code'] >= 500) ? 'danger' : 'warning';
$payload = [
'text' => '🚨 Security Event Detected',
'attachments' => [
[
'color' => $color,
'fields' => [
[
'title' => 'Endpoint',
'value' => '`' . $data['endpoint'] . '`',
'short' => true
],
[
'title' => 'Action',
'value' => '`' . $data['action'] . '`',
'short' => true
],
[
'title' => 'IP Address',
'value' => $data['ip_address'],
'short' => true
],
[
'title' => 'Status Code',
'value' => (string) $data['status_code'],
'short' => true
],
[
'title' => 'User Agent',
'value' => substr($data['user_agent'], 0, 100),
'short' => false
]
],
'footer' => 'Init Sentinel',
'ts' => strtotime($data['created_at'])
]
]
];
wp_remote_post( $webhook_url, [
'headers' => ['Content-Type' => 'application/json'],
'body' => wp_json_encode($payload),
'timeout' => 5,
'blocking' => false,
] );
}
Module 4: Telegram Bot Alert
Telegram allows receiving alerts on mobile instantly, suitable for mobile admins.
/**
* Send alert via Telegram Bot API
* @param array $data Log data
*/
function init_html_alert_telegram( $data ) {
$bot_token = apply_filters('init_html_telegram_bot_token', '');
$chat_id = apply_filters('init_html_telegram_chat_id', '');
if ( empty($bot_token) || empty($chat_id) ) {
return;
}
$emoji = ($data['status_code'] >= 500) ? '🔴' : '🟡';
$message = sprintf(
"%s *Security Event Detected*\n\n" .
"*Endpoint:* `%s`\n" .
"*Action:* `%s`\n" .
"*IP:* `%s`\n" .
"*Status:* `%d`\n" .
"*User:* %s\n" .
"*Time:* %s UTC",
$emoji,
$data['endpoint'],
$data['action'],
$data['ip_address'],
$data['status_code'],
$data['user_id'] ?: 'Guest',
$data['created_at']
);
$url = sprintf(
'https://api.telegram.org/bot%s/sendMessage',
$bot_token
);
wp_remote_post( $url, [
'body' => [
'chat_id' => $chat_id,
'text' => $message,
'parse_mode' => 'Markdown'
],
'timeout' => 5,
'blocking' => false,
] );
}
Alert Router: Deciding which alert to send, when
Not every log needs an alert. Alert Router is the logic layer that determines severity and appropriate channels.
/**
* Router: decide whether to send alert and through which channel
*/
add_action('init_html_security_event', function( $log_id, $data ) {
// 1) Whitelist: skip some trusted IPs/users
$whitelist_ips = apply_filters('init_html_alert_whitelist_ips', []);
if ( in_array($data['ip_address'], $whitelist_ips, true) ) {
return;
}
// 2) Critical endpoints → alert immediately via all channels
$critical_endpoints = apply_filters('init_html_alert_critical_endpoints', [
'inkstone/delete-manga',
'inkstone/create-admin',
'inkstone/bulk-delete',
]);
foreach ($critical_endpoints as $ep) {
if ( strpos($data['endpoint'], $ep) !== false ) {
init_html_alert_discord($data);
init_html_alert_telegram($data);
init_html_alert_email($data);
return;
}
}
// 3) Brute force detection: same IP, >5 times in 10 minutes
global $wpdb;
$table = $wpdb->prefix . 'init_sentinel_security_log';
$count = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$table}
WHERE ip_address = %s
AND status_code = 403
AND created_at > (UTC_TIMESTAMP() - INTERVAL 10 MINUTE)",
$data['ip_address']
) );
if ( $count >= 5 ) {
$data['action'] .= ' (Brute Force Pattern Detected)';
init_html_alert_discord($data);
init_html_alert_slack($data);
// Auto-block IP (needs separate implementation)
do_action('init_html_auto_block_ip', $data['ip_address'], '24 hours');
return;
}
// 4) 5xx errors → email only (don't spam chat)
if ( $data['status_code'] >= 500 ) {
init_html_alert_email($data);
return;
}
// 5) All other cases: no alert, just log
}, 10, 2);
Rate Limiting for Alerts: Avoid notification spam
If an attacker spams 100 requests/second, admin will receive 100 alerts. Need throttling.
/**
* Throttle alert: only send max 1 alert/IP per 5 minutes
*/
function init_html_should_send_alert( $ip_address ) {
$cache_key = 'init_sentinel_alert_sent_' . md5($ip_address);
$cache_group = 'init_html_sentinel';
$last_sent = wp_cache_get($cache_key, $cache_group);
if ( $last_sent !== false ) {
return false; // Already sent alert for this IP recently
}
// Mark as sent, cache for 5 minutes
wp_cache_set($cache_key, time(), $cache_group, 5 * MINUTE_IN_SECONDS);
return true;
}
// Use in router
add_action('init_html_security_event', function( $log_id, $data ) {
if ( ! init_html_should_send_alert($data['ip_address']) ) {
return; // Throttled
}
// ... send alert logic as usual
}, 5, 2); // Priority 5 to run before main router
Configuring webhook URLs via Settings API
Hardcoding webhook URLs in code is bad practice. Need UI for admin config.
// Register settings
add_action('admin_init', function() {
register_setting('init_html_sentinel_alerts', 'init_html_discord_webhook');
register_setting('init_html_sentinel_alerts', 'init_html_slack_webhook');
register_setting('init_html_sentinel_alerts', 'init_html_telegram_bot_token');
register_setting('init_html_sentinel_alerts', 'init_html_telegram_chat_id');
register_setting('init_html_sentinel_alerts', 'init_html_alert_email');
});
// Add settings page
add_action('admin_menu', function() {
add_submenu_page(
'init-html-security-logs',
__('Alert Settings', 'init-html'),
__('Alert Settings', 'init-html'),
'manage_options',
'init-html-alert-settings',
'init_html_render_alert_settings_page'
);
});
function init_html_render_alert_settings_page() {
if ( ! current_user_can('manage_options') ) {
return;
}
?>
<div class="wrap">
<h1><?php echo esc_html__('Alert & Notification Settings', 'init-html'); ?></h1>
<form method="post" action="options.php">
<?php settings_fields('init_html_sentinel_alerts'); ?>
<table class="form-table">
<tr>
<th><label for="discord_webhook">Discord Webhook URL</label></th>
<td>
<input type="url" id="discord_webhook" name="init_html_discord_webhook"
value="<?php echo esc_attr(get_option('init_html_discord_webhook', '')); ?>"
class="regular-text" placeholder="https://discord.com/api/webhooks/..."/>
<p class="description">Get webhook URL from Server Settings → Integrations → Webhooks</p>
</td>
</tr>
<tr>
<th><label for="slack_webhook">Slack Webhook URL</label></th>
<td>
<input type="url" id="slack_webhook" name="init_html_slack_webhook"
value="<?php echo esc_attr(get_option('init_html_slack_webhook', '')); ?>"
class="regular-text" placeholder="https://hooks.slack.com/services/..."/>
</td>
</tr>
<tr>
<th><label for="telegram_token">Telegram Bot Token</label></th>
<td>
<input type="text" id="telegram_token" name="init_html_telegram_bot_token"
value="<?php echo esc_attr(get_option('init_html_telegram_bot_token', '')); ?>"
class="regular-text" placeholder="123456:ABC-DEF..."/>
</td>
</tr>
<tr>
<th><label for="telegram_chat">Telegram Chat ID</label></th>
<td>
<input type="text" id="telegram_chat" name="init_html_telegram_chat_id"
value="<?php echo esc_attr(get_option('init_html_telegram_chat_id', '')); ?>"
class="regular-text" placeholder="-100123456789"/>
<p class="description">Use @userinfobot to get your chat ID</p>
</td>
</tr>
<tr>
<th><label for="alert_email">Alert Email</label></th>
<td>
<input type="email" id="alert_email" name="init_html_alert_email"
value="<?php echo esc_attr(get_option('init_html_alert_email', get_option('admin_email'))); ?>"
class="regular-text"/>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
// Filter webhook URLs from settings
add_filter('init_html_discord_webhook_url', function() {
return get_option('init_html_discord_webhook', '');
});
add_filter('init_html_slack_webhook_url', function() {
return get_option('init_html_slack_webhook', '');
});
add_filter('init_html_telegram_bot_token', function() {
return get_option('init_html_telegram_bot_token', '');
});
add_filter('init_html_telegram_chat_id', function() {
return get_option('init_html_telegram_chat_id', '');
});
add_filter('init_html_alert_email_recipients', function() {
return get_option('init_html_alert_email', get_option('admin_email'));
});
Testing Alert System: Send test notifications
Admins need to test if webhooks work before real deployment.
// Add test buttons to settings page
function init_html_render_alert_settings_page() {
// ... form as above
// Handle test requests
if ( isset($_POST['test_discord']) && check_admin_referer('init_html_test_alert') ) {
$test_data = [
'endpoint' => 'test/endpoint',
'action' => 'test_alert',
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1',
'status_code' => 403,
'user_id' => get_current_user_id(),
'user_agent' => 'Init Sentinel Test',
'created_at' => gmdate('Y-m-d H:i:s'),
];
init_html_alert_discord($test_data);
echo '<div class="notice notice-success"><p>Discord test notification sent!</p></div>';
}
// Test buttons
echo '<hr><h2>Test Notifications</h2>';
echo '<form method="post">';
wp_nonce_field('init_html_test_alert');
echo '<button type="submit" name="test_discord" class="button">Test Discord</button> ';
echo '<button type="submit" name="test_slack" class="button">Test Slack</button> ';
echo '<button type="submit" name="test_telegram" class="button">Test Telegram</button> ';
echo '<button type="submit" name="test_email" class="button">Test Email</button>';
echo '</form>';
}
Conclusion
Alert & Notification System transforms Init Sentinel from a passive logger into an active defender. Combined with pattern detection and auto-blocking, this system allows admins to respond within minutes instead of hours or days.
Comments