← 所有文章
教程

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 建议异步处理事件。持久化和 worker 的实现仍由应用负责。应遍历数组,而非只读取第一个元素。

按失败环节排查

观察结果下一步检查
应用没有收到请求配置的域名与路径、TLS、入口路由、代理日志
签名验证失败channel secret 是否正确、原始字节是否保留、请求头是否传递
已验签的空请求触发异常是否无条件读取首个事件、消息或 reply token
应用显示成功,但 Verify 失败公网入口实际返回的状态与请求耗时
Verify 成功,收件箱却没有消息真实事件的持久化、路由与 worker 执行

不要仅因一个只适配消息的处理器拒绝空数组,就轮换凭证或更换 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 却失败?

常见实现问题是默认每个请求都有首条消息事件。应核对实际响应与异常日志;网络传输或签名错误也可能导致测试失败。

验证成功是否证明机器人能回复?

不能。它检查的是接收端通信,不是存储、worker 执行、回复资格或发送 API 的执行结果。

下一步与参考资料

官方账号接入先运行 LINE Webhook URL 检查,再追踪一条真实测试消息。独立的 UnifyPort 接入则应实现其文档规定的 Webhook 契约。

官方资料核对日期:2026-10-28。

UnifyPort API

让消息接入变成一条稳定的产品管线。

先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。