2026年6月24日
2026年6月24日
WordPressのWebhookを実装して外部サービスと連携する方法
はじめに
WebhookはWordPressで特定のイベントが発生したとき(記事の公開・コメント投稿など)に外部サービスへリアルタイム通知を送る仕組みです。ポーリングと異なりサーバー負荷が低く、Slack通知・Zapier連携・Discordボットなど幅広い用途に活用できます。本記事では送信・受信両方のWebhook実装とセキュリティ対策を5ステップで解説します。
症状・原因
- 記事を公開したことをチームメンバーにリアルタイムで通知したい
- ZapierやMakeなどの外部自動化ツールとWordPressを連携させたい
- 外部システムからWordPressのコンテンツを自動更新したいが安全な受信口がない
解決手順
ステップ1:記事公開時にSlack/DiscordへWebhookを送信する
<?php
// 記事が公開されたときにSlackに通知を送る
add_action( 'publish_post', 'my_notify_slack_on_publish', 10, 2 );
function my_notify_slack_on_publish( $post_id, $post ) {
// 自動保存とリビジョンはスキップする
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return;
}
// Slack Incoming Webhook URLを設定する(環境変数や管理画面設定から取得する)
$slack_webhook_url = get_option( 'my_slack_webhook_url', '' );
if ( empty( $slack_webhook_url ) ) {
return; // URLが未設定の場合はスキップする
}
$post_title = get_the_title( $post_id );
$post_url = get_permalink( $post_id );
$author = get_the_author_meta( 'display_name', $post->post_author );
$thumbnail = get_the_post_thumbnail_url( $post_id, 'medium' ) ?: '';
// Slackのメッセージペイロードを組み立てる
$payload = [
'username' => get_bloginfo( 'name' ),
'icon_emoji' => ':wordpress:',
'attachments' => [
[
'color' => '#0073aa', // WordPress管理画面のブルー
'title' => $post_title,
'title_link' => $post_url,
'text' => "新しい記事が公開されました :tada:\n著者: {$author}",
'image_url' => $thumbnail,
'footer' => get_bloginfo( 'name' ),
'ts' => time(),
],
],
];
// wp_remote_postでSlackにPOSTリクエストを送る
wp_remote_post( $slack_webhook_url, [
'headers' => [ 'Content-Type' => 'application/json' ],
'body' => wp_json_encode( $payload ),
'timeout' => 10,
'blocking' => false, // 非同期で送信してページ表示を遅らせない
'data_format' => 'body',
] );
}
ステップ2:HMACシグネチャでWebhookのセキュリティを確保する
<?php
// 送信するWebhookにHMAC-SHA256署名を付けて受信側が検証できるようにする
function my_send_signed_webhook( $url, $payload_data ) {
$payload_json = wp_json_encode( $payload_data );
// HMAC-SHA256でシグネチャを生成する
$secret = get_option( 'my_webhook_secret', '' ); // 共有シークレット
$timestamp = time();
// タイムスタンプとペイロードを結合してハッシュ化する(リプレイ攻撃対策)
$signature_string = "{$timestamp}.{$payload_json}";
$signature = hash_hmac( 'sha256', $signature_string, $secret );
$response = wp_remote_post( $url, [
'headers' => [
'Content-Type' => 'application/json',
// X-Signatureヘッダーで署名を送信する
'X-Signature' => "t={$timestamp},v1={$signature}",
'X-Webhook-ID' => wp_generate_uuid4(), // 重複排除用ID
],
'body' => $payload_json,
'timeout' => 15,
] );
return $response;
}
// 受信側での署名検証関数
function my_verify_webhook_signature( $payload, $signature_header, $secret ) {
// ヘッダーからタイムスタンプと署名を分解する
parse_str( str_replace( ',', '&', $signature_header ), $parts );
$timestamp = $parts['t'] ?? 0;
$received_sig = $parts['v1'] ?? '';
// タイムスタンプが5分以内かチェックする(リプレイ攻撃対策)
if ( abs( time() - $timestamp ) > 300 ) {
return false;
}
// 期待されるシグネチャを計算する
$expected_sig = hash_hmac( 'sha256', "{$timestamp}.{$payload}", $secret );
// タイミング攻撃を防ぐhash_equalsで比較する
return hash_equals( $expected_sig, $received_sig );
}
ステップ3:Webhook受信エンドポイントをREST APIで実装する
<?php
// 外部サービスからのWebhookを受け取るREST APIエンドポイントを登録する
add_action( 'rest_api_init', 'my_register_webhook_endpoint' );
function my_register_webhook_endpoint() {
register_rest_route( 'my-plugin/v1', '/webhook/receive', [
'methods' => WP_REST_Request::METHOD_POST,
'callback' => 'my_handle_incoming_webhook',
// 認証はシグネチャで行うため公開エンドポイントにする
'permission_callback' => '__return_true',
] );
}
function my_handle_incoming_webhook( WP_REST_Request $request ) {
// リクエストボディを文字列として取得する
$payload = $request->get_body();
$sig_header = $request->get_header( 'x-signature' );
$secret = get_option( 'my_webhook_secret', '' );
// シグネチャを検証する
if ( ! my_verify_webhook_signature( $payload, $sig_header, $secret ) ) {
return new WP_REST_Response(
[ 'error' => '署名が無効です' ],
401
);
}
$data = json_decode( $payload, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return new WP_REST_Response( [ 'error' => 'JSONパースエラー' ], 400 );
}
// イベントの種類に応じて処理を分岐する
$event = $data['event'] ?? '';
switch ( $event ) {
case 'order.completed':
// 注文完了イベントを処理する
my_process_order_completed( $data );
break;
case 'user.registered':
// ユーザー登録イベントを処理する
my_process_user_registered( $data );
break;
default:
// 未知のイベントはログに記録する
error_log( "Unknown webhook event: {$event}" );
}
// 受信成功を返す(外部サービスがリトライしないように素早く200を返す)
return new WP_REST_Response( [ 'status' => 'received' ], 200 );
}
ステップ4:失敗したWebhookのリトライ処理を実装する
<?php
// Webhookの送信に失敗した場合は指数バックオフでリトライする
function my_send_webhook_with_retry( $url, $payload, $attempt = 1 ) {
$max_attempts = 3; // 最大3回リトライする
$response = wp_remote_post( $url, [
'headers' => [ 'Content-Type' => 'application/json' ],
'body' => wp_json_encode( $payload ),
'timeout' => 15,
] );
$status_code = wp_remote_retrieve_response_code( $response );
$is_success = ! is_wp_error( $response ) && $status_code >= 200 && $status_code < 300;
if ( ! $is_success && $attempt < $max_attempts ) {
// 指数バックオフ(2^attempt * 60秒)で次のリトライをスケジュールする
$delay = pow( 2, $attempt ) * 60; // 120秒, 240秒, ...
wp_schedule_single_event(
time() + $delay,
'my_retry_webhook',
[ $url, $payload, $attempt + 1 ] // 引数をarrayで渡す
);
// 失敗をログに記録する
my_log_webhook_attempt( $url, $payload, $status_code, false );
return false;
}
my_log_webhook_attempt( $url, $payload, $status_code, $is_success );
return $is_success;
}
// スケジュールされたリトライイベントのハンドラ
add_action( 'my_retry_webhook', 'my_send_webhook_with_retry', 10, 3 );
ステップ5:WebhookのログをカスタムDBテーブルに記録する
<?php
// Webhookログ用のカスタムテーブルを作成する(プラグイン有効化時に実行)
register_activation_hook( __FILE__, 'my_create_webhook_log_table' );
function my_create_webhook_log_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'webhook_logs';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table_name} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
webhook_url VARCHAR(500) NOT NULL,
event_type VARCHAR(100) DEFAULT '',
payload LONGTEXT DEFAULT '',
response_code INT DEFAULT 0,
success TINYINT(1) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY success (success),
KEY created_at (created_at)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql ); // テーブルが存在しない場合のみ作成する
}
// Webhookの送受信ログをDBに記録するヘルパー関数
function my_log_webhook_attempt( $url, $payload, $status_code, $success ) {
global $wpdb;
$wpdb->insert(
$wpdb->prefix . 'webhook_logs',
[
'webhook_url' => substr( $url, 0, 500 ), // URLを500文字に制限する
'event_type' => $payload['event'] ?? '',
'payload' => wp_json_encode( $payload ),
'response_code' => (int) $status_code,
'success' => $success ? 1 : 0,
'created_at' => current_time( 'mysql' ),
],
[ '%s', '%s', '%s', '%d', '%d', '%s' ]
);
}
注意事項
- シークレットの管理: Webhookシークレットは
wp-config.phpの定数かサーバー環境変数に保存し、データベースに平文で保存しないようにしてください。 - 非同期送信:
blocking => falseを使うと送信結果を確認できませんが、ページ表示速度には影響しません。重要な通知はblocking => trueでエラーを確認してください。 - ログの保持期間: Webhookログは定期的に古いレコードを削除するcronジョブを設定してDBが肥大化しないようにしてください。
まとめ
Webhookの送受信をHMACシグネチャで保護し、失敗時のリトライ処理とDBログを組み合わせることで、堅牢な外部連携システムを構築できます。Slack・Discord・Zapier・MakeなどあらゆるWebhook対応サービスと安全に連携可能になります。関連記事:WordPressのカスタム投稿タイプにACFを活用する方法