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