← 所有文章
教學

按需載入 WhatsApp 較早訊息:歷史請求與非同步回調

透過 UnifyPort 載入 WhatsApp 較早訊息時,應先訂閱 conversation.history,再以已儲存訊息建立 before 錨點發出請求。HTTP 回應不會包含訊息內容:202 和 status: accepted 只表示請求已獲接受。可用歷史會非同步送達。因此,應實作盡力提供結果的**「載入較早訊息」**按鈕,而不是完整封存匯出工具,也不要用自動分頁循環宣稱歷史已全部載入。

重點

  • 此操作適用於 provider=whatsapp 私人對話,不適用於群組、頻道或 whatsapp-protocol。
  • 發出請求前,先完成事件訂閱及持久化接收。
  • 使用真實原生內容訊息的 ID、傳送時間及方向更新錨點。
  • 批次可能重複、延遲、多次送達,亦可能沒有結果;沒有回調不等於完成。
  • 歷史訊息用來補充時間線,不應觸發新訊息自動回覆。

分清請求回應與歷史結果

請求對話歷史參考 定義了 POST /v1/accounts/{account_id}/conversations/history/request。它啟動非同步推送,並非直接傳回已存訊息的 REST 讀取介面。

觀察結果可以確認甚麼不能確認甚麼
HTTP 202、data.status: accepted請求已獲接受訊息已送達或操作已完成
HTTP request_id可用於排查該次 HTTP 請求任務 ID 或回調關聯鍵
conversation.history 帶有 data.history.source: on_demand收到了按需歷史批次這是唯一或最後一個批次
空批次或較短批次該批次包含的內容可用歷史已經結束
HTTP 逾時客戶端未收到明確回應請求沒有產生作用

訊息帳戶執行階段恢復指南 處理的是恢復連線,與補充歷史不同。重新連線或請求歷史,都不能保證補回中斷期間遺漏的全部訊息。

先準備接收端,再開放按鈕

將 conversation.history 加入端點訂閱,同時保留收件箱原有的必要事件。即時訊息繼續由 message.received 接收。更新現有端點時,依照 webhook 設定參考 操作,不要意外清空 signing_secret。

遵循 投遞規格:以 X-Device-Timestamp、一個句點及原始請求本文組成簽署輸入,用 HMAC-SHA256 驗證 X-Device-Signature。檢查時間戳是否在允許範圍內,持久化經驗證的資料後才回應 2xx。

歷史事件需要獨立的去重分支。WhatsApp HistorySync 的不同分塊可能重用頂層事件 ID,不能只因 ID 曾出現,就丟棄整個批次。在工作區內,應按 provider、account_id、data.conversation.id 及每項 data.messages[].id 合併訊息。

不要把歷史內容交給即時自動回覆觸發器。今天收到舊問題,不表示客戶今天再次提問。

建立有效的 before 錨點

選擇同一訊息帳戶、同一私人對話中已觀察到的訊息。錨點需要 message_id、sent_at 和 direction。不要根據對話標題、接收時間或電話號碼猜測這些值。

以下是符合文件結構的示範請求本文;請以實際儲存的值取代示例識別值:

{
  "conversation_id": "100000000000002@lid",
  "before": {
    "message_id": "MSG_HISTORY_ANCHOR_001",
    "sent_at": "2026-09-28T03:00:00Z",
    "direction": "inbound"
  },
  "limit": 50
}

使用後端保存的 X-Api-Key 呼叫上述 POST 操作。account_id 必須是一個非空路徑段,不含空白、斜線或編碼後的斜線。不要把對話識別值放進帳戶路徑段。

繼續請求時,從已收到的訊息中選擇時間最早、錨點欄位完整的原生內容訊息,排除 type: call 合成記錄。以下 JavaScript 只從已選定並驗證的訊息建立錨點,不是完整接收端或自動分頁任務:

function beforeFromMessage(message) {
  if (!message || message.type === 'call' ||
      typeof message.id !== 'string' || !message.id ||
      typeof message.sent_at !== 'string' ||
      !Number.isFinite(Date.parse(message.sent_at)) ||
      !['inbound', 'outbound'].includes(message.direction)) {
    throw new Error('Select a native content message with complete anchor fields');
  }
  return {
    message_id: message.id,
    sent_at: message.sent_at,
    direction: message.direction
  };
}

注意,type 不會複製到 before。沒有有效錨點時,停用請求並解釋原因,不要使用合成通話記錄或虛構較早時間。

在收件箱表達不確定性

以下是建議的應用程式行為,不是額外的 API 狀態:

  1. **發送前:**記錄帳戶、對話、錨點及本地請求時間。按對話依次執行操作員請求,減少重疊工作。
  2. **接受後:**顯示「請求已接受,等待可用歷史」。即時訊息繼續獨立接收。
  3. **每次批次到達:**以訊息為單位作冪等合併。保留即時編輯狀態及刪除屏障,避免舊歷史覆蓋較新狀態。
  4. **收到較早內容後:**允許操作員明確提出下一次請求,使用最早的合格錨點。如果沒有更早的合格錨點,顯示「尚未收到更早錨點」,而不是「全部歷史已載入」。
  5. **逾時或沒有回調:**保留不確定狀態,繼續接受延遲批次。不要自動重發同一請求。

文件沒有定義下一頁游標或完成狀態。account.history.synced 是 HistorySync 單一批次或分塊的摘要,不能證明某次按需請求已結束,更不能證明封存完整。不要將它視為自行推定的任務完成訊號。

歷史中的引用關係也不等於傳送能力。歷史訊息不含 reply_token;引用回覆指南 解釋了為何不能用父訊息 ID 取代它。同樣,不可用的附件應標示為不可用,而不是顯示成已下載的檔案。

錯誤處理與驗收檢查

400 可能表示 invalid_request、provider_invalid_request(包括合成通話錨點),或 unsupported_conversation_type。其他 provider 回傳 501 unsupported_by_provider。應修正適用範圍或輸入,而不是建立重試循環。保存 HTTP request_id 供排查使用,但不要用作回調關聯鍵。

上線前,建議測試重複批次、不同分塊共用同一事件 ID、HTTP 逾時後才到達的回調、即時訊息與歷史交錯,以及錨點缺少方向的情況。這些是建議測試,不是已取得的結果。

UnifyPort 提供非官方介面。此功能用於盡力補充對話背景,不提供完整備份、保證重播或任意帳戶存取。仍須維護自己的授權訊息儲存及保留規則。

常見問題

202 代表較早訊息已取得嗎?

不是。它只代表請求已獲接受。可用訊息會透過非同步歷史事件送達。

可以一直請求,直至收到較短批次嗎?

不要以短批次作為結束條件。批次大小不能證明完整性,自動請求亦可能與延遲結果重疊。

能用於 WhatsApp 群組、LINE 或 Zalo 嗎?

此操作只適用於 provider=whatsapp 私人對話。統一 webhook 結構不代表各平台有相同的歷史請求能力。

下一步與參考資料

先按 請求對話歷史參考 實作接收端及不確定狀態,再啟用收件箱按鈕。

官方參考核對日期:2026-09-30。

UnifyPort API

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

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