Verify 按鈕第一次就接受的 Webhook 網址。

主控台只接受 https 網址,Verify 按鈕要看到 200;重新傳送預設關閉,除非你找到那個開關;而 LINE 從不說明它多久重試一次。AnyHook 替你回應 Verify,在邊緣節點儲存每個事件,並依你看得到的排程持續重試處理程式。

時限問題

LINE Developers Console 不會儲存非 https 的 Webhook URL,旁邊的 Verify 按鈕會送出一個真正的 POST,而且必須回 200。用隧道能通過檢查,直到主機名稱換掉為止;之後你的機器人就指向一個不存在的地方,主控台也不會告訴你。更深層的問題是漏接時會發生什麼事:Webhook 重新傳送預設是關閉的,就算開啟了,LINE 也明說重試次數與間隔不公開,而且可能隨時變更、不另行通知。

回覆這一端有自己的時鐘。reply token 的有效期限是一分鐘,而且只能用一次,所以伺服器晚一步才拿到的事件(當機、部署或重試之後),就沒辦法再用免費的回覆呼叫來回應。如果機器人伺服器無法連線的時間夠長,LINE 可能會完全停止傳送 Webhook 給它,而這個門檻同樣沒有公開。

關閉
重新傳送的出廠設定,漏接的 Webhook 就沒了
200
網址儲存前,Verify 按鈕必須看到的狀態碼
1 分鐘
reply token 的有效期限,只能用一次
?
LINE 公布的重試次數與間隔:沒有

在主控台指向 AnyHook,只驗一個標頭

把 App(一個接收網址,對應一個 Webhook 來源)的來源設為 LINE,貼上 Channel Secret:AnyHook 會在邊緣節點檢查 x-line-signature,也就是原始請求主體(raw body)的 base64 HMAC-SHA256;偽造的請求既到不了你這裡,也不會計入配額。每次傳遞都會以新的 AnyHook-Signature 重新簽章。

// 1. In AnyHook: set the app's source to LINE, paste your Channel Secret.
//
// 2. In LINE Developers Console -> Messaging API:
//    Webhook URL: https://in.anyhook.net/{user-slug}/{app-slug}
//    Press Verify (AnyHook answers 200), enable "Use webhook",
//    and turn ON "Webhook redelivery" while you are there.

import { verifyWebhook } from "anyhook-verify";

export async function POST(req: Request) {
  const ok = await verifyWebhook(req, process.env.ANYHOOK_SIGNING_SECRET!);
  if (!ok) return new Response("invalid signature", { status: 401 });

  const event = await req.json();
  for (const e of event.events ?? []) {
    if (e.type !== "message") continue;

    const answer = await draftAnswer(e.message); // an LLM call, a few seconds

    // Reply tokens die after one minute and work once. On a retried
    // delivery the token is already dead, so fall back to push instead
    // of dropping the answer on the floor.
    try {
      await lineReply(e.replyToken, answer);
    } catch {
      await linePush(e.source.userId, answer);
    }
  }
  return new Response("ok", { status: 200 });
}

團隊常問的問題

主控台的 Verify 按鈕一直失敗,它要的是什麼?
它會對網址送出一個 events 陣列為空的 POST,並期待透過 https 收到單純的 200。本機隧道時好時壞,主機名稱一換就壞。AnyHook 的接收網址(inbound URL)是穩定的 https 端點,請求持久寫入佇列後就回應,所以 Verify 第一次就會通過,之後也一直通過。還有一點要知道:LINE 會像對待其他 Webhook 一樣,用你的 Channel Secret 簽署這個驗證用的 POST,所以如果來源已設為 LINE,Verify 卻仍然失敗,通常是密鑰打錯了。AnyHook 會在邊緣節點以 401 拒收這些請求:App 頁面會計算數量,每天的前 5 筆會連同標頭,以已拒收的狀態保存在事件紀錄裡,讓你看得到失敗原因。重新貼上密鑰,再按一次 Verify。
x-line-signature 由誰驗證,AnyHook 還是我的伺服器?
App 的來源設為 LINE 並設定好 Channel Secret 時,由 AnyHook 在邊緣節點驗證:檢查不通過的請求,在到達伺服器或消耗配額之前就會被拒收。接著你的伺服器只要驗證統一格式的 AnyHook-Signature 標頭。原本的 x-line-signature 仍會原封不動地轉送;而因為 LINE 的機制只簽署請求主體、沒有時間戳記,重試時它依然有效,你想在下游再檢查一次也可以。
傳遞重試時,reply token 會怎樣?
幾乎一定已經過期,因為 token 的壽命是從原始 Webhook 起算一分鐘。可行的做法是:第一次、快速的嘗試用 reply 端點回應,reply 呼叫失敗時改用 push 端點。無論哪種情況,AnyHook 都會保存完整的事件主體,所以晚到的處理程式仍然清楚知道對方說了什麼。
我的機器人伺服器放在會休眠的免費主機上,回覆還來得及送出嗎?
無論如何,LINE 大約 1 秒就會從 AnyHook 收到 200,所以 LINE 看不到任何失敗。接著 AnyHook 會立刻把事件送往你的伺服器,等它醒來,Free 方案最多等 60 秒。如果它在這段時間內回應,事件就會在第一次嘗試時送達,晚了幾秒,而從原始事件起有效一分鐘的 reply token 通常仍然可用。如果喚醒花的時間比 token 的一分鐘還長,就由上面程式碼裡的 push 備援來回應。如果伺服器完全沒有及時回應,下一次嘗試會在 5 分鐘後。
我的機器人用 Python 寫,要怎麼檢查 AnyHook-Signature?
標頭的內容是 t=<unix seconds>,v1=<hex>。用目的地的簽章密鑰(儀表板的 App → Destinations → Signing secret)計算 f"{t}.{raw_body}" 的 HMAC-SHA256,用 hmac.compare_digest 與 v1 比對,並拒絕 t 超過 5 分鐘的請求。如果你的處理程式已經在檢查 x-line-signature,也可以繼續用它:LINE 只簽署請求主體,所以重試時依然有效。
AnyHook 已經替我重試了,還需要開啟 LINE 自己的重新傳送嗎?
需要。兩者負責不同的路段。AnyHook 重試的是事件儲存之後、從 AnyHook 到你伺服器的這一段。LINE 的重新傳送負責從 LINE 到 AnyHook 的這一段;在少數 AnyHook 無法保存事件、刻意回 503 讓發送端再試一次的情況下,這一段就很重要。

換一個網址。LINE 的處理程式不用改。

Free 方案每月涵蓋 10,000 個事件。不用 SDK:把 LINE 指向你的 AnyHook 接收網址。