如何在統一 Webhook 中處理訊息表情回應
處理訊息表情回應時,請訂閱 message.reaction,先用原始請求內容驗證 Webhook 簽章,再以 data.message.target_message_id 找出被回應的原訊息。表情位於 data.event.reaction;空字串代表使用者已移除回應。寫入狀態之前,還要用最上層事件 ID 去重。
重點整理
data.message.id代表回應本身,不是原訊息。data.message.target_message_id代表收到表情回應的訊息。data.event.reaction保存表情;""代表移除。- JSON 解析前應驗證
X-Device-Signature。 - 各平台支援程度不同,合法的訂閱名稱不代表每個平台都會產生此事件。
看懂 message.reaction 載荷
UnifyPort 將表情回應正規化為標準 message.reaction 事件。團隊可以把 👍 當成確認、把 👎 送往人工檢查,或只在共享收件匣同步顯示表情。這些都是應用規則;事件本身只回報狀態變化。
官方標準 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": "👍" }
}
}
這裡的三個 ID 用途不同。data.message.id 代表回應記錄,data.message.target_message_id 指向原訊息,最上層 id 則代表標準 Webhook 事件。此範例應把 👍 關聯到 wamid.HBgM,而不是 wamid.HBgZ。
儲存目前狀態,而不只追加事件
事件日誌適合稽核與除錯,但收件匣介面通常需要目前狀態。可用 provider、account_id、data.conversation.id、data.message.target_message_id 與 data.sender.id 組成狀態鍵。
data.event.reaction 非空時,儲存該傳送者對目標訊息的目前表情;若是空字串,就刪除對應狀態。不要把空字串存成新的表情。
更新投影前,先把最上層事件寫入可靠佇列或資料庫,並對最上層 id 設定唯一限制。這可避免合法重送造成重複變更。如果現有接收器只訂閱入站訊息,可依照 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>.<原始請求內容> 的十六進位 HMAC-SHA256。不可先解析 JSON 再重新序列化。完整作法請看 Webhook 傳送與簽章驗證,重送、時間戳與持久化去重則可搭配 HMAC 重送防護與冪等教學。
設定訂閱與錯誤處理
只處理表情回應的消費者可使用:
{
"subscribed_events": ["message.reaction"]
}
共享收件匣通常同時訂閱 message.received 與 message.reaction。只有能處理全部公開標準事件的通用收集器,才適合使用 "*"。
事件可靠寫入後再回傳 2xx;簽章無效時拒絕請求;結構不完整的回應事件應進入可觀測的錯誤流程。若要把表情作為核准依據,請先查看 各平台 Webhook 事件差異。
UnifyPort 適合的位置
UnifyPort 透過非官方介面,把支援平台的事件正規化成同一個信封結構。應用程式可以先依 event.type 分流,再於支援 message.reaction 的平台共用一套狀態處理邏輯。
正規化不會創造上游平台未提供的能力。如果表情事件是合規核准或不可缺少的紀錄,應逐一確認每個平台;官方平台 API 的事件契約更合適時,就採用官方方案。
常見問題
哪個欄位是原訊息 ID?
使用 data.message.target_message_id。data.message.id 代表回應本身。
如何判斷表情已被移除?
檢查 data.event.reaction。空字串表示移除;非空字串就是目前表情。
可以用 target_message_id 去重嗎?
不行。多人可以回應同一則訊息,同一人也能更換表情。投遞去重使用最上層事件 id;目前狀態鍵則包含目標訊息與傳送者。
每個平台都會傳送 message.reaction 嗎?
不會。事件名稱有效與平台實際支援是兩件事,上線前請查閱事件矩陣。
下一步
開啟 建立 Webhook 端點參考,將 message.reaction 加入 subscribed_events,設定 signing_secret,並測試新增及移除表情。
來源
官方第一手來源,查核日期:2026 年 8 月 20 日。