2026年8月17日

2026年8月17日

WordPressのイベントをSlack・Discordにリアルタイム通知する方法

はじめに

WordPressサイトで新しい記事が公開された、コメントが投稿された、不正ログインの試みがあったという情報をリアルタイムでSlackやDiscordに通知したいケースは多いです。WordPress Webhookとwp_remote_post()を組み合わせることで、プラグインなしでこれらの通知を実装できます。本記事では実用的な通知システムの構築方法を解説します。

症状・原因

  • 新しい投稿が公開されてもチームへの周知が遅れ、SNSシェアのタイミングを逃している
  • 不正ログインの試みが繰り返されていることに気づかずにいる
  • コメントのスパムや不審なコメントへの対応が遅れている

解決手順

ステップ1:Slack Incoming Webhookを設定してテストする

# 1. Slack App作成: https://api.slack.com/apps
# 2. "Incoming Webhooks"を有効化
# 3. "Add New Webhook to Workspace"でチャンネルを選択
# 4. Webhook URLをコピー

# curlでWebhookのテスト(コマンドライン)
curl -X POST \
     -H 'Content-type: application/json' \
     --data '{"text":"WordPressからのテスト通知 :wordpress:"}' \
     https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX

# 成功時のレスポンス
# ok
<?php
// wp-config.phpまたは環境変数でWebhook URLを管理
// (セキュリティのためfunctions.phpにハードコードしない)
define( 'SLACK_WEBHOOK_URL', 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL' );
define( 'DISCORD_WEBHOOK_URL', 'https://discord.com/api/webhooks/YOUR/WEBHOOK/URL' );

ステップ2:新規投稿公開時にSlack通知を送る

<?php
// publish_postアクション(投稿が公開されたときに発火)
add_action( 'publish_post', 'notify_slack_on_new_post', 10, 2 );
function notify_slack_on_new_post( $post_id, $post ) {
    // 自動保存と一括更新を除外
    if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
        return;
    }

    // 既に通知済みかチェック(二重通知を防ぐ)
    if ( get_post_meta( $post_id, '_slack_notified', true ) ) {
        return;
    }

    // 投稿情報を取得
    $post_url    = get_permalink( $post_id );
    $post_title  = get_the_title( $post_id );
    $author_name = get_the_author_meta( 'display_name', $post->post_author );
    $category    = implode( ', ', wp_get_post_categories( $post_id, array( 'fields' => 'names' ) ) );

    // Slackのブロックメッセージ形式で送信
    $payload = array(
        'blocks' => array(
            array(
                'type' => 'header',
                'text' => array(
                    'type' => 'plain_text',
                    'text' => '新しい記事が公開されました',
                ),
            ),
            array(
                'type' => 'section',
                'fields' => array(
                    array( 'type' => 'mrkdwn', 'text' => "*タイトル*\n{$post_title}" ),
                    array( 'type' => 'mrkdwn', 'text' => "*著者*\n{$author_name}" ),
                    array( 'type' => 'mrkdwn', 'text' => "*カテゴリ*\n{$category}" ),
                    array( 'type' => 'mrkdwn', 'text' => "*URL*\n<{$post_url}|記事を見る>" ),
                ),
            ),
        ),
    );

    // Slackに送信
    $result = send_webhook_notification( SLACK_WEBHOOK_URL, $payload );

    if ( $result ) {
        // 通知済みフラグを保存(二重通知防止)
        update_post_meta( $post_id, '_slack_notified', current_time( 'mysql' ) );
    }
}

// Webhook送信の共通関数
function send_webhook_notification( $webhook_url, $payload ) {
    $response = wp_remote_post(
        $webhook_url,
        array(
            'method'      => 'POST',
            'timeout'     => 10,
            'headers'     => array( 'Content-Type' => 'application/json' ),
            'body'        => wp_json_encode( $payload ),
        )
    );

    if ( is_wp_error( $response ) ) {
        error_log( 'Webhook Error: ' . $response->get_error_message() );
        return false;
    }

    return 200 === wp_remote_retrieve_response_code( $response );
}

ステップ3:新規コメント投稿時にSlack通知を送る

<?php
// comment_postアクション(コメントが保存されたときに発火)
add_action( 'comment_post', 'notify_slack_on_comment', 10, 3 );
function notify_slack_on_comment( $comment_id, $comment_approved, $commentdata ) {
    // スパムコメントは通知しない
    if ( 'spam' === $comment_approved ) {
        return;
    }

    $comment  = get_comment( $comment_id );
    $post     = get_post( $comment->comment_post_ID );
    $post_url = get_permalink( $comment->comment_post_ID );

    // 承認待ちと承認済みで絵文字を変える
    $status_emoji = ( 1 == $comment_approved ) ? ':white_check_mark:' : ':clock3:';
    $status_text  = ( 1 == $comment_approved ) ? '承認済み' : '承認待ち';

    $payload = array(
        'text' => sprintf(
            '%s 新しいコメント(%s)' . "\n" .
            '投稿: <%s|%s>' . "\n" .
            '投稿者: %s <%s>' . "\n" .
            'コメント: %s',
            $status_emoji,
            $status_text,
            $post_url,
            $post->post_title,
            esc_html( $comment->comment_author ),
            esc_html( $comment->comment_author_email ),
            wp_trim_words( $comment->comment_content, 20, '...' )
        ),
    );

    send_webhook_notification( SLACK_WEBHOOK_URL, $payload );
}

ステップ4:Discord Webhookの異なるペイロード形式で通知する

<?php
// Discordのembedフォーマット(Slackと形式が異なる)
function send_discord_notification( $title, $description, $url = '', $color = 3447003 ) {
    // Discordのembed形式
    $payload = array(
        'embeds' => array(
            array(
                'title'       => $title,
                'description' => $description,
                'url'         => $url,
                'color'       => $color, // 16進数の色コード(3447003 = #3498DB)
                'footer'      => array(
                    'text' => get_bloginfo( 'name' ) . ' | ' . current_time( 'Y-m-d H:i' ),
                ),
            ),
        ),
    );

    return send_webhook_notification( DISCORD_WEBHOOK_URL, $payload );
}

// 投稿公開時にDiscordにも通知
add_action( 'publish_post', 'notify_discord_on_new_post', 10, 2 );
function notify_discord_on_new_post( $post_id, $post ) {
    if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
        return;
    }

    $title       = get_the_title( $post_id );
    $url         = get_permalink( $post_id );
    $excerpt     = get_the_excerpt( $post_id ) ?: wp_trim_words( $post->post_content, 30 );
    $author      = get_the_author_meta( 'display_name', $post->post_author );

    send_discord_notification(
        '📝 新しい記事: ' . $title,
        "**著者**: {$author}\n**概要**: {$excerpt}",
        $url,
        0x0073AA // WordPressブルー
    );
}

ステップ5:ログイン失敗通知でセキュリティ監視を行う

<?php
// wp_login_failedアクション(ログイン失敗時に発火)
add_action( 'wp_login_failed', 'notify_failed_login', 10, 2 );
function notify_failed_login( $username, $error ) {
    // IPアドレスを取得(プロキシ対応)
    $ip_address = '';
    foreach ( array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' ) as $key ) {
        if ( ! empty( $_SERVER[ $key ] ) ) {
            $ip_address = sanitize_text_field( $_SERVER[ $key ] );
            break;
        }
    }

    // 同一IPからの失敗回数をTransientで追跡
    $fail_key   = 'login_fail_' . md5( $ip_address );
    $fail_count = (int) get_transient( $fail_key );
    $fail_count++;
    set_transient( $fail_key, $fail_count, HOUR_IN_SECONDS );

    // 5回以上失敗したときだけ通知(スパム防止)
    if ( $fail_count < 5 ) {
        return;
    }

    $message = sprintf(
        ':warning: *ログイン失敗アラート*' . "\n" .
        'ユーザー名: `%s`' . "\n" .
        'IPアドレス: `%s`' . "\n" .
        '失敗回数: *%d回*(過去1時間)' . "\n" .
        '時刻: %s',
        esc_html( $username ),
        esc_html( $ip_address ),
        $fail_count,
        current_time( 'Y-m-d H:i:s' )
    );

    send_webhook_notification( SLACK_WEBHOOK_URL, array( 'text' => $message ) );

    // 10回以上でDiscordにも通知(より深刻な警告として)
    if ( $fail_count >= 10 ) {
        send_discord_notification(
            '⚠️ ブルートフォース攻撃の可能性',
            "IP: {$ip_address}\nユーザー名: {$username}\n失敗回数: {$fail_count}回/時",
            '',
            0xFF0000 // 赤
        );
    }
}

注意事項

  • Webhook URLの秘匿: SlackとDiscordのWebhook URLはwp-config.phpの定数または環境変数で管理し、リポジトリにコミットしないこと
  • 二重通知防止: publish_postは記事の更新でも発火するため、_slack_notifiedメタフィールドで通知済み管理が必要です
  • レート制限: Discordは30秒に50リクエストの制限があります。大量の通知が発生する環境ではキューイングを検討してください
  • タイムアウト: wp_remote_post()のタイムアウトを短く設定し(5〜10秒)、ページ表示速度への影響を最小化してください

まとめ

WordPressのアクションフック(publish_postcomment_postwp_login_failed)とwp_remote_post()を組み合わせることで、Slack・Discord両方へのリアルタイム通知システムを構築できます。ログイン失敗の監視はセキュリティ強化にも有効です。関連記事:WordPressのセキュリティを強化する方法

お気軽にご相談ください

お見積りへ お問い合わせへ