Init Sentinel – Part 7: Alert & Notification System – Instant Response to Threats

After having the ability to log, monitor and analyze, the next step that determines Init Sentinel’s real-world effectiveness is the alert system. An unread log is no different from an unpatched vulnerability.

Init Sentinel – Part 7: Alert & Notification System – Instant Response to Threats

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


  • No comments yet.

Init Toolbox

Press Ctrl + \ on desktop, or swipe left anywhere on mobile.

Login