← 所有文章
教學

處理 UnifyPort 批量送達與已讀回執

一個 UnifyPort 送達或已讀回執可能涵蓋多條訊息。有批量欄位時,應處理 data.message.ids 中的每個 ID;單條回執則使用 data.message.id。將回執與訊息內容分開儲存,即使回執先到也不會遺失。發送請求獲接受、收件人回執,以及團隊收件箱的已讀狀態,是三種不同證據,不能共用一個「成功」標記。

重點

  • 按工作空間、渠道、訊息帳戶及對話配對回執,不能只看訊息 ID。
  • 渠道回報的回執時間與 webhook 到達時間分開儲存。
  • 遲到的送達回執不能清除已有的已讀證據。
  • 缺少確認代表未知,不代表可以重新發送。

先確認批量範圍,再更新介面

標準事件規格將 message.delivered 定義為訊息送達收件人的裝置,將 message.read 定義為收件人已讀回執。批量欄位是 data.message.ids;只更新單數 id 會漏掉其餘訊息。

證據能確認甚麼不能確認甚麼
發送回應 status: accepted請求已獲接受送達或已讀
message.delivered指定訊息已送達收件人已閱讀
message.read指定訊息已讀客服個案已解決或所有群組成員已讀
對話本地已讀狀態已連接帳戶的聊天清單狀態收件人回執

最後一項屬於獨立的共用收件箱已讀/未讀流程。不要呼叫對話已讀操作來修復缺少的外發訊息回執。

以下是根據已讀回執文件縮減的說明例子,並非實際客戶事件:

{
  "id": "evt_2f9c1a4b7e",
  "type": "message.read",
  "provider": "whatsapp",
  "account_id": "acc_8c21d0",
  "occurred_at": "2026-06-08T12:35:10Z",
  "data": {
    "conversation": { "id": "8613912345678", "type": "user" },
    "sender": { "id": "8613912345678", "type": "user" },
    "message": {
      "id": "wamid.HBgM",
      "ids": ["wamid.HBgM", "wamid.HBgN"],
      "read_at": "2026-06-08T12:35:10Z"
    }
  }
}

這個回執涵蓋清單中的兩條訊息,並不證明更早的訊息都已讀。即使 ID 看起來是數字,也不能自行推導連續範圍。

先保存觀察紀錄,再產生狀態標記

建議分為兩層:持久保存的已驗證事件紀錄,以及按訊息整理的回執檢視。這是應用程式的儲存設計,不是額外的 UnifyPort 欄位。

  1. 將兩個回執事件名稱加入訂閱,同時保留其他必要事件及簽署設定。
  2. 按webhook 投遞規格,使用 signing_secret 驗證「時間戳、一個點、原始請求本文」的 HMAC-SHA256,並檢查簽署時間戳是否過舊。
  3. 持久保存已驗證事件後才回傳 2xx。事件紀錄與持久任務應以原子方式提交;亦可將檢視更新與去重紀錄放入同一交易。
  4. 將批量回執展開為逐訊息觀察紀錄。保留工作空間脈絡、provider、account_id、data.conversation.id、事件 ID,以及可用的發送者身分。
  5. 分別儲存 occurred_at、渠道的 read_at 或 delivered_at,以及應用程式自己的到達時間。回執時間亦會鏡像至 data 層的同名欄位。缺少的時間保持未知,不能以到達時間冒充渠道時間。

以下 JavaScript 只從已驗證簽署的事件擷取訊息範圍,不是完整接收器或資料庫實作:

function receiptMessageIds(event) {
  if (!['message.delivered', 'message.read'].includes(event.type)) return [];
  const message = event.data?.message;
  const ids = message?.ids === undefined ? [message?.id] : message.ids;
  if (!Array.isArray(ids) || ids.length === 0 ||
      ids.some(id => typeof id !== 'string' || id.length === 0)) {
    throw new Error('Receipt scope needs review');
  }
  return [...new Set(ids)];
}

批量欄位無效時,不要默默改用單數 ID。應保留事件供調查,而不是按猜測的範圍更新。

合併證據,不製造送達結論

獨立保留送達與已讀觀察。已讀回執先到時,可以顯示「已觀察到已讀」,但不要編造 delivered_at。其後到達的送達事件只補充證據,不能把介面降回「已送達」。重複事件亦不應產生重複通知。

若訊息內容尚未儲存,先按完整訊息識別保留回執,待配對紀錄出現後再附加。不能只憑回執建立入站訊息或增加收件箱計數。可用 occurred_at 與事件 ID 建立確定性順序,但不要採用「最後到達的狀態覆蓋全部」的邏輯。

發送時,將回應中的 message_id 及 provider_ref 與本地操作一起保存。關聯回執前,確認哪個識別碼對應實際觀察到的渠道訊息;不同欄位名稱並不保證值永遠相等。無法配對的回執應進入核對佇列,不要按時間就近附加到某條訊息。保留可用的發送者身分,但不要將一次觀察擴大為「所有人已讀」。

媒體發送疑難排解指南處理的是來源檔案及請求接受階段。回執處理屬另一層,逾時或沒有回執都不能自動觸發重發。

能力界限與驗收情境

UnifyPort 使用非官方介面。渠道事件矩陣描述解析器對應能力,不保證每個帳戶或部署都產生回執。在介面承諾送達標記前,應核對所選渠道,包括 LINE。沒有回執不能證明未讀、未送達或被封鎖;遺漏事件亦不保證重播。

建議測試:涵蓋兩條訊息的批量回執、單條回執、重複投遞、已讀先於送達、回執先於內容、不同帳戶使用相同訊息 ID、無效批量欄位,以及一直沒有回執。確認只有明確列出的 ID 被更新,且沒有任何情境自動重發。這些是待執行測試,不是已有的測試結果。

常見問題

應處理 id 還是 ids?

有有效且非空的批量 ids 時使用它;否則使用單數 id。無效範圍應交由調查流程處理。

收到已讀回執後,可以補填 delivered_at 嗎?

不可以。顯示已讀證據即可,不要製造渠道送達時間。

沒有回執能把發送標記為失敗嗎?

單憑這一點不行。保留「確認未知」,檢查渠道能力、訂閱、接收流程及識別碼配對。

下一步與參考資料

從送達回執文件開始,先實作有明確範圍的觀察紀錄儲存,再加入介面狀態。

產品資料核對日期:2026-11-06。

UnifyPort API

令訊息接入變成一條穩定嘅產品管線。

先用統一嘅 API 跑通發送,再用標準事件將所有入站訊息接返去業務系統。