按需載入 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 狀態:
- **發送前:**記錄帳戶、對話、錨點及本地請求時間。按對話依次執行操作員請求,減少重疊工作。
- **接受後:**顯示「請求已接受,等待可用歷史」。即時訊息繼續獨立接收。
- **每次批次到達:**以訊息為單位作冪等合併。保留即時編輯狀態及刪除屏障,避免舊歷史覆蓋較新狀態。
- **收到較早內容後:**允許操作員明確提出下一次請求,使用最早的合格錨點。如果沒有更早的合格錨點,顯示「尚未收到更早錨點」,而不是「全部歷史已載入」。
- **逾時或沒有回調:**保留不確定狀態,繼續接受延遲批次。不要自動重發同一請求。
文件沒有定義下一頁游標或完成狀態。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。
令訊息接入變成一條穩定嘅產品管線。
先用統一嘅 API 跑通發送,再用標準事件將所有入站訊息接返去業務系統。