← 所有文章
教學

為排隊回覆實作 WhatsApp 24 小時發送護欄

排隊中的 WhatsApp 回覆可能在等待審批、重試或 worker 領取工作期間跨過 24 小時時段。可靠的發送護欄必須在實際發送前重新讀取伺服器端狀態,以最近一則已驗證的用戶訊息計算期限,並阻擋已過期的自由文字。訊息類別與收費規則可先查閱服務與實用訊息決策樹;本文只處理避免誤發的執行邊界。

重點

  • 保存兩個到期時間及單調遞增的 state_version,不要只保存布林狀態。
  • 只有已驗證、已去重的用戶入站訊息可以重設 24 小時時段。
  • worker 領取工作後,應在同一交易或鎖內重新讀取狀態並判斷發送路徑。
  • 已過期的排隊草稿應停止並重新分流,不能靜默轉成範本。
  • 以重複事件、亂序事件、並行 worker 及秒級邊界測試驗證護欄。

圍繞排隊回覆建立 WhatsApp 發送護欄

WhatsApp 官方商業平台收費頁面說明,用戶訊息會開啟或重設 24 小時客服時段。實作層只需把這條許可邊界設為最終發送條件;2026 年 10 月的訊息分類與收費變更由現有決策指南獨立說明,本文不再重複。

發送器執行每個排隊工作時必須回答三個實作問題:

  1. 讀到的是最新狀態版本嗎? 舊 worker 不可覆蓋更新後的時段。
  2. 實際發送時時段仍開放嗎? 草稿建立或審批時的判斷不能沿用。
  3. 選擇的發送路徑仍獲授權嗎? 時段關閉後只可使用真正符合且已審批的範本,否則停止並交回人工。

使用兩個時鐘,而不是單一狀態

請保存明確時間戳,讓重試、延誤工作及客服交接都能重新計算。

保存欄位何時開啟或重設控制內容
service_window_expires_at每一則用戶入站訊息是否可發送非範本服務回覆
free_entry_expires_at符合資格的 Click-to-WhatsApp 廣告或 Facebook 專頁行動按鈕入口72 小時內是否免除訊息送達費
last_user_message_id每一則已接受的用戶入站訊息現有時段的冪等及審計依據
state_version每個改變狀態的入站事件避免排隊發送器覆蓋較新狀態

72 小時免費入口不是延長版客服時段。用戶在這段期間再次傳訊息,可以重設 24 小時許可時鐘,但原有免費入口到期點仍然獨立。

實作發送護欄

以下是應用層 TypeScript 例子,不是 Meta webhook payload。它刻意分開「是否允許」及「是否預期收費」:

type WindowState = {
  serviceWindowExpiresAt: Date | null;
  freeEntryExpiresAt: Date | null;
};

function evaluateWhatsAppSend(state: WindowState, now: Date) {
  const serviceWindowOpen =
    state.serviceWindowExpiresAt !== null && now < state.serviceWindowExpiresAt;
  const freeEntryActive =
    state.freeEntryExpiresAt !== null && now < state.freeEntryExpiresAt;

  return {
    maySendNonTemplate: serviceWindowOpen,
    expectedDeliveryCharge: serviceWindowOpen && !freeEntryActive,
    requiredPath: serviceWindowOpen ? "service" : "approved_template",
  } as const;
}

這個判斷必須成為最終發送護欄,而不只是畫面提示。10:00 寫好的回覆可能在審批隊列停留到時段關閉。發送器應在領取工作時重新讀取狀態,再選擇:

  1. 時段仍開放時發送非範本服務回覆。
  2. 時段關閉後,在業務意圖符合已審批範本時改用範本。
  3. 兩條路都不合法時停止並交回真人客服。

不要暗中把自由文字改成範本,亦不要為配合範本而截斷內容。範本類別、變數及審批是獨立契約。

處理入站事件時不要錯誤延長時段

只有用戶傳來的訊息才可重設客服時段。送達回執、訊息狀態、客服草稿、內部備註、重試及企業端訊息 echo 都不可延長它。

建議依次處理:

  1. 先驗證 provider webhook,再改變狀態。
  2. 用穩定的訊息或事件識別碼去重。
  3. 確認事件是用戶發送的入站訊息,而不是回執或 echo。
  4. service_window_expires_at 設為已接受訊息時間加 24 小時。
  5. 只有符合資格的 referral 才建立獨立 72 小時免費入口。
  6. 增加 state_version,保存來源事件供審計。
  7. 每次真正發送前重新計算兩個時鐘。

不要在舊到期時間上繼續加 24 小時。用戶 09:00 傳訊息、12:00 再傳一則時,新到期點應為翌日 12:00。

測試排隊回覆與事件順序邊界

以固定時鐘做驗收測試,讓一秒鐘邊界也可重現。

情況預期結果
用戶 10:00 傳訊息;翌日 09:59:59 回覆允許非範本服務回覆
同一訊息;翌日剛好 10:00 回覆當作時段已關閉
翌日 09:50 用戶再傳訊息到期點重設為再翌日 09:50
到期前獲批、到期後才領取的排隊回覆阻擋非範本發送並重新分流
同一用戶訊息重複送達兩次去重後只產生一次狀態轉移
較舊的用戶訊息遲過新訊息抵達保留較新的到期點,不讓狀態倒退
兩個 worker 同時領取同一排隊回覆只有一個 worker 通過版本檢查並發送

上線後,應將系統預判與官方狀態及收費記錄對帳。服務訊息費用追蹤指南說明如何拆分送達量與市場費率。若正在比較 Meta Business Agent 及自建 AI 流程,請閱讀收費與架構比較,不要把 AI 選項混入這個狀態機。

UnifyPort 的位置

上述狀態機適用於官方 WhatsApp Business Platform。UnifyPort 的非官方接口是一般訊息帳號的另一條接入路徑,不提供 Meta 客服時段或 Pricing Analytics 狀態。

在這條獨立路徑上,WhatsApp 入站訊息會以標準 message.received 事件送達。若 webhook 端點設定了 signing_secret,應先用原始 request body 驗證 X-Device-TimestampX-Device-Signature;標準支援範圍內的回覆使用 POST /v1/messages。詳見webhook 投遞及簽署文件message.received 事件文件

不要把 UnifyPort 事件放進官方 Cloud API 收費狀態機後聲稱已由 Meta 分類。若團隊同時使用兩條路徑,請保存清晰的 transportcontrol_plane 欄位並分開帳本。

限制與取捨

需要已審批範本、推廣活動工具、Click-to-WhatsApp 歸因、Meta 原生分析或 BSP 託管流程時,應選擇官方 WhatsApp Business Platform。官方平台亦是判斷官方送達是否收費的事實來源。

非官方接口不能審批範本、延長 Meta 時段、提供 Meta Pricing Analytics 或改變 WhatsApp 政策。它的價值是為一般訊息帳號提供另一條接入路徑,並統一多平台事件。兩條路徑都需要伺服器端冪等、並行控制及審計記錄。

費率及產品規則具時效性。10 月 1 日切換前請重新核對 Meta 官方文件,也不要把規劃費率寫死在狀態轉移邏輯內。

常見問題

發送護欄應使用哪個時間戳?

使用已驗證並被系統接受的最新用戶訊息時間;不要使用 webhook 接收時間、工作建立時間或客服畫面的倒數計時。

亂序 webhook 會縮短時段嗎?

不應。用穩定事件識別碼去重,只在入站訊息時間晚於目前記錄時更新到期點及 state_version

排隊回覆在發送前過期怎麼辦?

停止自由文字發送並重新分流。只有業務意圖確實符合已審批範本時才可改走範本,否則交回人工。

可以自動把過期自由文字轉成範本嗎?

不可靜默轉換。範本類別、變數及審批是獨立契約,必須明確選擇並驗證。

如何避免重複事件觸發兩次發送?

持久化訊息或事件識別碼,並讓 worker 在交易或鎖內檢查冪等鍵及 state_version 後才發送。

下一步

先完成正確的入站邊界:閱讀webhook 投遞及簽署文件,再測試重複或亂序事件不會錯誤延長發送時段。狀態轉移可靠後,再接上訊息分類。

來源

官方來源核實日期:2026 年 7 月 27 日。