統合 Webhook でメッセージリアクションを処理する方法
リアクションを処理するには、message.reaction を購読し、リクエストの生データで Webhook 署名を検証してから、data.message.target_message_id をリアクション対象のメッセージ ID として使います。絵文字は data.event.reaction に入り、空文字列なら削除です。状態を更新する前に、トップレベルのイベント ID で重複も排除します。
要点
data.message.idはリアクション自体を識別し、元メッセージの ID ではありません。- 元メッセージは
data.message.target_message_idで特定します。 data.event.reactionが絵文字を保持し、""は削除を表します。- JSON を解析する前に
X-Device-Signatureを検証します。 - プロバイダーごとに対応状況が異なるため、有効なイベント名でも必ず発生するとは限りません。
message.reaction ペイロードを読む
UnifyPort はリアクションを標準の message.reaction イベントに正規化します。たとえば 👍 を確認済み、👎 を担当者レビューとして扱うことも、LINE を含む複数チャネルの共有受信箱に現在の絵文字を表示するだけにすることもできます。これらはアプリ側のルールであり、イベントそのものは変化した内容を伝えます。
公式の標準 Webhook イベントリファレンスにある message.reaction の主要な形は次のとおりです。
{
"id": "evt_2f9c1a4b7e",
"type": "message.reaction",
"provider": "whatsapp",
"account_id": "acc_8c21d0",
"occurred_at": "2026-06-08T12:35:40Z",
"data": {
"conversation": { "id": "8613912345678", "type": "user" },
"sender": { "id": "8613912345678", "type": "user", "name": "Jordan Lee" },
"message": { "id": "wamid.HBgZ", "target_message_id": "wamid.HBgM" },
"event": { "kind": "message_reaction", "reaction": "👍" }
}
}
3 つの ID は役割が異なります。data.message.id はリアクションのレコード、data.message.target_message_id は元メッセージ、トップレベルの id は標準 Webhook イベントを示します。この例では 👍 を wamid.HBgZ ではなく wamid.HBgM に関連付けます。
イベントだけでなく現在の状態を保存する
監査や調査には追記型ログが便利ですが、受信箱の UI が必要とするのは通常、現在のリアクション状態です。状態キーには次を組み合わせられます。
provideraccount_iddata.conversation.iddata.message.target_message_iddata.sender.id
data.event.reaction が空でなければ絵文字を保存し、空文字列なら対象メッセージに対するその送信者のリアクションを削除します。空文字列を新しい絵文字として保存しないでください。
投影を更新する前にイベントを永続キューまたはデータベースへ入れ、トップレベルの id に一意制約を設けます。これで正規の再配信による二重更新を防げます。現在の受信エンドポイントが message.received だけを購読している場合は、Webhook イベントフィルターの解説を使って message.reaction を追加できます。
Node.js で状態を適用する
次の中核ロジックは API リファレンスの実在するフィールドを使っています。Map は説明用であり、本番ではトランザクションと一意制約を持つデータベースに置き換えてください。
const event = JSON.parse(rawBody.toString('utf8'));
if (event.type === 'message.reaction') {
const { conversation, sender, message, event: detail } = event.data;
if (!message?.target_message_id || typeof detail?.reaction !== 'string') {
throw new Error('Invalid message.reaction payload');
}
const key = [event.provider, event.account_id, conversation.id,
message.target_message_id, sender.id].join(':');
if (detail.reaction === '') reactionState.delete(key);
else reactionState.set(key, {
emoji: detail.reaction,
reactionMessageId: message.id,
occurredAt: event.occurred_at,
});
}
この処理は必ず署名検証の後に実行します。エンドポイントに signing_secret がある場合、X-Device-Signature は <X-Device-Timestamp>.<リクエストの生データ> に対する 16 進表現の HMAC-SHA256 です。JSON を先に解析して再シリアライズしてはいけません。完全な手順は Webhook 配信と署名検証にあり、タイムスタンプと永続的な冪等性は HMAC と再配信対策のチュートリアルで確認できます。
購読とエラー処理を決める
リアクション専用のコンシューマーなら次の設定で十分です。
{
"subscribed_events": ["message.reaction"]
}
受信箱では通常、message.received と message.reaction の両方を購読します。"*" は、すべての公開標準イベントを処理できる汎用コレクターに限って使うのが安全です。
永続的に受理してから 2xx を返し、無効な署名は拒否します。構造が不正なイベントは、誤ったメッセージを書き換えず、監視できるエラー経路へ送ります。絵文字を承認の唯一の根拠にする前に、プロバイダー別 Webhook イベント表を確認してください。
UnifyPort が適する範囲
UnifyPort は非公式インターフェースとして、対応するメッセージングプラットフォームのイベントを共通のエンベロープに正規化します。アプリは event.type で分岐し、message.reaction が利用できる接続で同じ状態更新ロジックを再利用できます。
正規化によって、上流プラットフォームにない機能が生まれるわけではありません。リアクションが監査や承認に不可欠なら、接続する各プラットフォームの対応状況を検証し、公式 API の契約が適している場合はそちらを選んでください。
FAQ
元メッセージを示すフィールドはどれですか?
data.message.target_message_id です。data.message.id はリアクション自体を示します。
リアクションの削除はどう判定しますか?
data.event.reaction が空文字列なら削除です。空でなければ現在の絵文字です。
target_message_id で重複排除できますか?
できません。同じメッセージに複数人が反応でき、同じ人が絵文字を変更することもあります。配信の重複排除にはトップレベルの id を使います。
すべてのプラットフォームが message.reaction を送りますか?
いいえ。有効なイベント名とプロバイダーの実対応は別です。本番投入前にイベント表を確認してください。
次のステップ
Webhook エンドポイント作成リファレンスを開き、subscribed_events に message.reaction を追加し、signing_secret を設定して、追加と削除の両方をテストしてください。
参照元
2026 年 8 月 20 日に確認した公式一次情報です。