← 所有文章
教學

LINE Webhook Verify 失敗?正確處理空 events 陣列

LINE 驗證 Webhook URL 時,會傳送一個 events 陣列為空的 HTTP POST 請求。接收端應先驗證簽章,再回傳 200,而不是讀取訊息或傳送回覆。如果處理程式假設 events[0] 一定存在,即使一般訊息能處理,Verify 測試也可能失敗。反過來,Verify 成功也不能證明實際訊息已進入佇列或客服收件匣。

重點

  • 通過簽章驗證的 events: [] 是有效的連線檢查,不代表訊息遺失。
  • 使用 channel secret 對原始請求主體驗證 x-line-signature,之後才解析 JSON。
  • 連線與業務處理必須分別測試,不能互相取代。
  • LINE 原生 Webhook 與 UnifyPort 標準事件使用不同契約。

LINE 的 Verify 按鈕實際傳送什麼

官方 Webhook URL 驗證指南提供兩種連線檢查方式:Webhook 測試 API,以及 LINE Developers Console 中的 Verify 按鈕。兩者檢查 LINE 能否與接收端通訊。對於不包含事件的請求,LINE 要求接收端回傳 200。

以下採用 LINE 簽章驗證範例中的請求主體結構,並非客戶訊息:

{"destination":"U8e742f61d673b39c7fff3cecb7536ef0","events":[]}

其中沒有可包含訊息、傳送者或 reply token 的事件物件。不要根據 destination 建立客戶資料,不要產生虛構的訊息識別碼,也不要排入自動回覆。若需要監控,應將此類通訊檢查與客戶訊息流量分開記錄。

修改 URL 前,先確認入口由誰負責。若多個工具共用同一個官方帳號,請先看多工具 Webhook 檢查清單,避免為了排查解析錯誤,把原本正常的流量移到另一個接收端。

先驗證簽章,再允許空陣列

LINE 的簽章驗證指南規定:以 channel secret 為金鑰,對收到的請求主體計算 HMAC-SHA256,再以 Base64 編碼比較簽章。這個金鑰不是呼叫傳送 API 時使用的 channel access token。

保留收到的原始位元組。先解析 JSON 再序列化,可能改變空白或跳脫字元,導致驗證失敗。events 為空也不能省略驗證,因為未經授權的請求同樣能建立這種 JSON。

以下 Node.js 輔助函式示範原生 LINE 請求的驗證與解析。rawBody 必須是 HTTP 框架保留的原始 Buffer,channelSecret 來自伺服器端金鑰儲存。它不是完整伺服器,也未實作持久化佇列。

import { createHmac, timingSafeEqual } from 'node:crypto';

export function readLineEvents(rawBody, signature, channelSecret) {
  if (!Buffer.isBuffer(rawBody) || !channelSecret) {
    throw new Error('Raw body and channel secret are required');
  }
  const expected = createHmac('sha256', channelSecret)
    .update(rawBody)
    .digest('base64');
  const supplied = Buffer.from(signature ?? '', 'utf8');
  const calculated = Buffer.from(expected, 'utf8');
  if (supplied.length !== calculated.length ||
      !timingSafeEqual(supplied, calculated)) {
    return { status: 401 };
  }
  try {
    const payload = JSON.parse(rawBody.toString('utf8'));
    if (!payload || !Array.isArray(payload.events)) {
      return { status: 400 };
    }
    return { status: 200, events: payload.events };
  } catch {
    return { status: 400 };
  }
}

這裡的 400、401 是範例自行選擇的防禦性回應,不是 LINE API 錯誤碼。整合時應依下列順序處理:

  1. 將 x-line-signature 標頭與未修改的主體傳入函式。
  2. 拒絕無效請求,不建立業務工作。
  3. 驗證成功且 events.length === 0 時,直接回傳 HTTP 200。
  4. 陣列非空時,先持久化收到的事件,再確認接收;耗時工作採非同步處理。儲存失敗時,不要只因函式回傳此狀態就送出 200。

LINE 建議非同步處理事件。持久化與背景工作程式仍由應用負責。應逐一處理陣列元素,而非只讀取第一個。

依失敗環節排查

觀察結果下一步檢查
應用沒有收到請求設定的主機與路徑、TLS、入口路由、代理伺服器紀錄
簽章驗證失敗channel secret 是否正確、原始位元組是否保留、標頭是否轉送
已驗證的空請求引發例外是否無條件讀取第一個事件、訊息或 reply token
應用顯示成功,但 Verify 失敗公開入口實際回傳的狀態與請求耗時
Verify 成功,收件匣卻沒有訊息實際事件的持久化、路由與背景工作執行

不要只因訊息處理程式拒絕空陣列,就輪替憑證或更換 Webhook 入口。若實際事件已送達,卻缺少 reply token,應另外排查:LINE standby 模式指南解釋的是回覆權限。空驗證請求沒有事件物件,也沒有事件 mode 可檢查。

分別驗證連線與訊息處理

以下是建議的驗收情境,不是已完成的正式環境測試結果:

  • 簽章正確的空請求回傳 200,不建立客戶訊息或傳送工作。
  • 相同空主體在缺少簽章或簽章錯誤時遭拒。
  • 簽章正確的非空請求,在確認接收前完成事件持久化。
  • Verify 成功後,向目標官方帳號傳送一則受控測試訊息,確認進入預期收件匣流程。
  • 儲存失敗時,不會只因簽章有效就被內部標示為處理完成。

診斷紀錄只保留必要資訊,例如簽章驗證結果、事件數量、HTTP 狀態與觀察時間。不要在一般紀錄中寫入 channel secret、reply token 或客戶訊息內容。

不要混用 UnifyPort 接收契約

UnifyPort 的非官方介面透過 LINE 授權流程連接 LINE 帳號,並不是官方帳號的原生 Webhook 測試服務。

其 Webhook 投遞契約使用 X-Device-Signature:設定 signing_secret 後,對 X-Device-Timestamp、一個句點及原始主體串接後的內容計算 HMAC-SHA256,並以十六進位編碼。標準信封包含 id、type、provider、account_id、occurred_at 和 data,不是 LINE 的 events 陣列。

兩種契約應使用不同路由或明確的接收設定。不能把 UnifyPort 請求中缺少原生 events 欄位,當成 LINE 空驗證請求。LINE Verify 成功不會驗證 UnifyPort 投遞、帳號授權或標準訊息處理。如果必須使用 LINE 官方帳號身分,應保留官方 Messaging API 路徑。

常見問題

events: [] 是錯誤嗎?

在 LINE 的 Webhook URL 驗證請求中不是。驗證簽章後回傳 200,不需要建立訊息工作。欄位缺少或型別錯誤與空陣列不同。

為什麼一般訊息能處理,Verify 卻失敗?

常見實作問題是假設每個請求都有第一個訊息事件。應核對實際回應與例外紀錄;網路傳輸或簽章錯誤也可能使測試失敗。

驗證成功是否證明機器人能回覆?

不能。它檢查接收端通訊,不是儲存、背景工作執行、回覆資格或傳送 API 的結果。

下一步與參考資料

官方帳號整合先執行 LINE Webhook URL 檢查,再追蹤一則實際測試訊息。獨立的 UnifyPort 整合則應實作其文件規定的 Webhook 契約。

官方資料核對日期:2026-10-28。

UnifyPort API

讓訊息接入變成一條穩定的產品管線。

先用統一 API 跑通傳送,再用標準事件把所有入站訊息接回業務系統。