← 所有文章
教學

如何在 WhatsApp 共用收件箱同步已讀與未讀狀態

WhatsApp 共用收件箱不應在 Webhook 一到達就把對話標為已讀,而應等團隊真正接手或完成處理後才更新。透過 UnifyPort,可以用 conversation_id 呼叫對話已讀端點;如要把 WhatsApp 回條準確推進至某則訊息,還要同時提交訊息 ID 及發送者 ID。需要再跟進時,則把對話標為未讀。

重點

  • 對話已讀及未讀動作目前只支援 WhatsApp;不支援該動作的渠道組合會回傳 501 unsupported_by_provider
  • POST /v1/accounts/{account_id}/conversations/read 必須接收 conversation_id,亦可選擇推進至指定訊息。
  • up_to_message_idup_to_message_sender_id 必須成對提交;只提交其中一個會回傳 400 invalid_request
  • 標記未讀只需要 conversation_id
  • 分配、跟進及完成狀態仍應保存在自己的系統。渠道端已讀狀態只是共用收件箱的投影,並非完整工單資料庫。

先分清三種「已讀」

可靠的共用收件箱要分開處理以下概念:

  1. 團隊佇列狀態:例如新訊息、已分配、等待中、已完成,由你的應用程式定義。
  2. 已連接 WhatsApp 帳戶的聊天清單狀態:已讀或未讀。透過標記對話已讀端點標記對話未讀端點更新。
  3. 收件人回條message.read Webhook 表示收件人讀過帳戶發出的一則或多則訊息,不等於客服開啟了入站工單。

本機對話設定改變時,UnifyPort 亦可以映射 conversation.updated 事件。data.conversation.id 識別聊天,已讀狀態變化可出現在 data.read。應把它視為核對訊號;至於誰接手、何時完成、為何重新開啟,仍要記錄在自己的資料庫。

處理任何事件前,先驗證原始請求內容的簽署,並讓重試具備冪等性。接收端安全邊界可參考Webhook HMAC、重播防護及重試指南。如果端點只需要收件箱事件,也可按Webhook 事件篩選教學明確訂閱所需事件。

決定何時更新渠道狀態

不要在每個入站 Webhook 抵達時自動標為已讀,否則無人處理的佇列看起來也像已清空。可採用以下明確規則:

團隊動作本機佇列狀態WhatsApp 動作
入站訊息已儲存new不操作
客服接手對話assigned按需要推進至已接手的訊息
客服完成對話resolved將整段對話標為已讀
客服要求後續跟進waiting將對話標為未讀
自動化在分配前失敗new不操作

這樣亦可避免瀏覽器重新整理、Webhook 重試或背景預覽意外清掉待辦。

將 WhatsApp 對話標為已讀

conversation_id 放在 JSON 請求內容,而不是 URL 路徑,因為渠道識別碼可能包含 @: 等字元。

將整段對話標為已讀:

curl -X POST "https://api.unifyport.ai/v1/accounts/$UNIFYPORT_ACCOUNT_ID/conversations/read" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "8613912345678@s.whatsapp.net"
  }'

如要把 WhatsApp 回條推進至明確的入站訊息,請從同一個 message.received 事件複製三個識別碼:

{
  "conversation_id": "120363041234567890@g.us",
  "up_to_message_id": "CURRENT-MESSAGE-ID",
  "up_to_message_sender_id": "8613912345678@lid"
}

群組訊息的 up_to_message_sender_id 必須使用相符的 data.sender.id,不可由對話 ID 推算。不需要訊息層級回條時,請同時省略兩個 up_to_message_* 欄位。

以下 Node.js helper 會先確認欄位成對,再發出請求:

const apiBase = 'https://api.unifyport.ai/v1';

async function setWhatsAppReadState({ accountId, conversationId, unread, message }) {
  const action = unread ? 'unread' : 'read';
  const body = { conversation_id: conversationId };

  if (!unread && message) {
    if (!message.id || !message.senderId) {
      throw new Error('message.id and message.senderId must be supplied together');
    }
    body.up_to_message_id = message.id;
    body.up_to_message_sender_id = message.senderId;
  }

  const response = await fetch(
    `${apiBase}/accounts/${encodeURIComponent(accountId)}/conversations/${action}`,
    {
      method: 'POST',
      headers: {
        'X-Api-Key': process.env.UNIFYPORT_API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    }
  );

  if (!response.ok) {
    const failure = await response.json();
    throw new Error(`${response.status} ${failure.error?.code ?? 'unknown_error'}`);
  }

  return response.json();
}

在已完成簽署驗證的事件處理器中,直接使用文件定義的欄位:

await setWhatsAppReadState({
  accountId: event.account_id,
  conversationId: event.data.conversation.id,
  unread: false,
  message: {
    id: event.data.message.id,
    senderId: event.data.sender.id
  }
});

將待跟進對話標為未讀

未讀動作更簡單:

await setWhatsAppReadState({
  accountId: event.account_id,
  conversationId: event.data.conversation.id,
  unread: true
});

只應在團隊明確重新開啟工作時使用。渠道端未讀狀態不能取代自己的提醒機制;跟進負責人、到期狀態及原因都應保存在本機佇列。

核對狀態時避免形成循環

應用程式呼叫對話動作後,Webhook 可能收到對應的 conversation.updated 事件。為本機發起的操作保存內部操作紀錄,讓後續事件用來確認狀態,而不是再次觸發同一動作。

安全流程如下:

  1. 以冪等方式儲存 message.received
  2. 在交易中更新本機工單狀態。
  3. 呼叫渠道狀態動作。
  4. API 回傳 { "data": { "ok": true } } 後才記錄成功。
  5. conversation.updated 視為確認,或視為來自已連接帳戶的外部變更。
  6. 狀態不明時,使用取得指定對話端點讀取該對話,再比較 unread_count

為其他渠道啟用控制項前,請查看目前的各渠道統一動作支援矩陣。統一路由不表示每個渠道都實作每一種動作。

限制與取捨

如果流程依賴渠道認證的企業功能、官方範本訊息或原生管治,官方方案可能更合適。UnifyPort 的非官方介面適用於一般帳戶訊息流程,但不會令所有渠道具備完全相同的功能。

就本文流程而言,對話已讀及未讀目前只支援 WhatsApp。其他渠道的共用收件箱應隱藏或停用這些控制項,不要把 501 unsupported_by_provider 當成可持續重試的暫時錯誤。

常見問題

收到 message.received 會自動將聊天標為已讀嗎?

不會。接收及儲存事件不應清空待辦;只有到達團隊定義的工作流程節點時,才呼叫已讀端點。

message.read 與標記對話已讀有何不同?

message.read 是收件人閱讀訊息後的回條事件;對話已讀動作改變的是已連接帳戶本機聊天清單狀態。

可以只提交 up_to_message_id 嗎?

不可以。必須連同 up_to_message_sender_id 一起提交;或兩者都省略,將整段對話標為已讀。

Telegram、LINE、TikTok、Zalo 及 X 能使用相同控制嗎?

目前不能。支援矩陣將對話已讀及未讀列為 WhatsApp 專屬動作;不支援的組合會回傳 501 unsupported_by_provider

下一步

先閱讀標記對話已讀 API Reference,並在定義本機重新開啟規則後,再加入未讀動作。

第一手來源

截至 2026 年 8 月 21 日核對: