所有文章
Discordwebhook debugginglocal testingbots

Discord Webhook 与交互端点的本地测试

Discord 有两样东西都被叫做“webhook”,但行为天差地别。一个是用来发消息的“发完即忘”URL。另一个是交互端点,每当用户运行斜杠命令时就会收到带签名的 POST。前者在 localhost 上能用,后者永远不行。本指南讲的是后者——因为它才需要隧道。

Discord 的两种 webhook

出站 webhook 是你向其 POST 的 URL。你发一条 JSON 消息,它出现在频道里。无需隧道,因为流量从你的机器向外流出。

交互端点 方向相反。你在 Discord 注册一个 URL;每当用户运行 /yourcommand,Discord 就向它 POST。该端点必须是 HTTPS、必须在三秒内响应,并对每个请求验证 Ed25519 签名。localhost 自身做不到这些。

为什么交互端点比大多数更严格

Discord 不仅要 HTTPS——它要求你在接受该 URL 前证明你控制该端点。验证发生在注册时:Discord 发送一个 PING 交互,你的服务器必须用带签名的 PONG 回应。只要 Ed25519 验证失败一次,Discord 就拒绝保存该 URL。

你第一次不会搞定 Ed25519 那部分。几乎没人能一次成功。

Ed25519 验证,最咬人的部分

Discord 使用 Ed25519(不是 HMAC SHA-256),并在每个交互上提供两个请求头:

  • X-Signature-Ed25519 — 十六进制签名
  • X-Signature-Timestamp — 以秒计的时间戳

你用 Discord 在开发者门户里展示的公钥对 timestamp + rawBody 进行签名。大多数语言都有相应的库。在 Node 中:

import nacl from 'tweetnacl';

const valid = nacl.sign.detached.verify(
  Buffer.from(timestamp + rawBody),
  Buffer.from(signature, 'hex'),
  Buffer.from(PUBLIC_KEY, 'hex'),
);

验证失败的头号原因——又是——在这段代码运行前就解析请求体的中间件。你需要 Discord 发来的原始字符串,而不是把解析后的对象重新序列化的版本。

分步:在 localhost 上的交互端点

  1. 在本地启动你机器人的 HTTP 服务器(任意端口)。
  2. 运行 npx portpreview 3000 获取一个公共 HTTPS 网址。
  3. 在 Discord 开发者门户打开你的应用,把隧道网址加上你的交互路径粘贴到 Interactions Endpoint URL
  4. 点击 Save。Discord 会立即发出一个 PING。如果你的 Ed25519 验证有效,URL 就会保存;否则该字段显示错误。
  5. 保存后,在任意有你机器人的服务器里运行一个斜杠命令。请求会落到你的处理器里。

如果保存反复失败,检查捕获到的请求——请求头、请求体和你服务器的响应。十有八九,请求体在验证前被一个 JSON 中间件悄悄改动了。

三秒期限

Discord 要求在三秒内响应,否则就把该交互算作失败。对于耗时更久的,先发一个 type-5 的初始响应(延迟),稍后再用 PATCH 给原始交互发真正的回复。本地测试一眼就能看出哪些处理器太慢——请求捕获会显示确切的延迟。

出站 webhook 仍然适合测试

如果你只是想从本地代码向频道发消息,出站 webhook URL 无需隧道即可工作。POST JSON,消息出现。我们用它从本地开发运行发送日志告警。

何时这套配置不够用了

对于只有少数命令的单个机器人,用隧道做本地测试绰绰有余。如果你维护一个服务数百万用户的公开机器人,你最终会想要一个带稳定 URL 的预发布部署,免得隧道会话轮换时 Discord 每次都要重新验证。

Discord 的三秒超时与 GitHub 的十秒窗口属于同一类问题。要看长篇版本,请阅读我们关于 webhook 重试与幂等性 的看法。加入 PortPreview 等候名单,在一个 CLI 里同时获得隧道与捕获。

常见问题

我能在 localhost 上测试 Discord 斜杠命令吗?
不能直接测。Discord 要求 HTTPS、Ed25519 签名验证以及三秒响应窗口。用隧道把你的本地服务器以 HTTPS 暴露,并在开发者门户里把它配置为交互端点。
为什么 Discord 拒绝我的交互端点 URL?
当你保存该 URL 时,Discord 会立即发出一个 PING 交互。如果你的 Ed25519 验证失败,或你的处理器没有用有效的 PONG 回应,保存就会被拒绝。最常见的原因是中间件在验证前解析了请求体。
出站 Discord webhook 需要隧道吗?
不需要。出站 webhook 是你从代码向其 POST 的 URL,所以流量只向外。只有交互端点(接收来自 Discord 的 POST)才需要隧道。