一个 Verify 按钮一次就通过的 Webhook URL。

控制台只接受 https URL,Verify 按钮要求返回 200;重新投递默认关闭,除非你找到那个开关;LINE 也从不说明它多久重试一次。AnyHook 应答 Verify 请求,在边缘节点保存每个事件,并按一个你真正看得到的计划持续重试你的处理程序。

时限问题

LINE Developers Console 不会保存非 https 的 Webhook URL,旁边的 Verify 按钮会发送一个真实的 POST 请求,必须返回 200。内网穿透工具能通过检查,直到主机名变化;之后你的机器人就指向了一个不存在的地址,而控制台不会告诉你。更深层的问题是漏收时会发生什么:Webhook 重新投递默认关闭;即使打开了,LINE 也明确表示重试次数和间隔不公开,而且可能随时变更,不另行通知。

回复这边也有自己的时钟。reply token 有效期一分钟,而且只能用一次,所以你的服务器在崩溃、部署或重试之后才拿到的事件,就不能再用免费的 reply 调用来回复了。如果机器人服务器长时间无法访问,LINE 可能会完全停止向它发送 Webhook,而这个阈值同样没有公布。

关闭
重新投递的出厂设置:漏掉的 Webhook 就没了
200
Verify 按钮必须收到它,URL 才能保存
1 分钟
reply token 的有效期,只能用一次
?
LINE 公布的重试次数和间隔:无

把控制台指向 AnyHook,只验证一个请求头

把 App 的来源设为 LINE,粘贴 Channel Secret:AnyHook 在边缘节点验证 x-line-signature(原始请求体的 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 按钮一直失败,它到底要什么?
它会向该 URL 发送一个 events 数组为空的 POST 请求,并期望通过 https 收到一个单纯的 200。本地内网穿透工具时好时坏,主机名一变就失效。AnyHook 的接收 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 让发送方重试,这时它就派上用场了。

只改一个 URL。LINE 处理程序不用动。

Free 套餐包含每月 10,000 个事件。无需 SDK:把 LINE 指向你的 AnyHook 接收 URL。