← 所有文章
教學

按需載入 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 跑通傳送,再用標準事件把所有入站訊息接回業務系統。