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 錯誤碼。整合時應按以下次序處理:
- 將
x-line-signature標頭及未修改的主體傳入函式。 - 拒絕無效請求,不建立業務工作。
- 驗證成功且
events.length === 0時,直接回傳 HTTP200。 - 陣列非空時,先持久化收到的事件,再確認接收;耗時工作採非同步處理。儲存失敗時,不要只因函式回傳此狀態便發送
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。
令訊息接入變成一條穩定嘅產品管線。
先用統一嘅 API 跑通發送,再用標準事件將所有入站訊息接返去業務系統。