概览
在你自己的应用中接收带签名的 HTTP 请求形式的 TicketWave 事件。
Webhooks
Webhook 是 TicketWave 发送给你 的 HTTP 请求。每当你的服务器里发生某件事——比如工单开启、成员被拉黑——TicketWave 就会向你拥有的一个 URL 发送带 JSON 请求体的 POST 请求。
这和 Log Channels 的区别在于:log channels 会把一个 embed 写入 Discord 供人阅读,而 webhooks 则是把原始事件直接交给你的代码处理。
Webhooks 是一项 高级 功能。没有高级版,就无法创建端点,也不会投递任何事件。
创建端点
打开端点页面
服务器控制台 → Webhooks → Endpoints。
添加端点
点击 Add Endpoint,然后填写两个字段:
| Field | Description |
|---|---|
| Endpoint URL | 接收请求的 https:// URL |
| Event Types | 这个端点应该接收哪些事件 |
一个端点只会接收你勾选的类型。不允许一个都不选——至少选择一个。
复制签名密钥
TicketWave 会在端点创建的那一刻生成一个签名密钥(whsec_…)。打开端点列表,点击眼睛图标显示它,然后把它复制到你应用的配置中。
把这个密钥当作密码一样对待。任何拿到它的人都可以伪造能通过你签名校验的请求。请把它放在环境变量里,绝不要放进仓库。
发送测试事件
在端点所在行使用 Send test event 按钮(纸飞机图标)。它会发送一条真实、完整签名的请求,并在载荷中包含 "test": true,这样你就能在真实工单依赖它之前确认接收端是否正常工作。
结果会像其他投递一样显示在 Webhooks 历史记录中。
端点要求
| Requirement | Detail |
|---|---|
| Scheme | 仅限 https:// — http:// 会被拒绝 |
| Host | 必须能被公网解析。私有地址、回环地址、链路本地地址和 CGNAT 地址都会被拒绝 |
| Response | 任意 2xx 状态都算成功 |
| Timeout | 你有 10 秒 的时间响应 |
| Redirects | 不会跟随。3xx 视为失败 |
| Limit | 每个服务器最多 5 个端点 |
主机检查会在你保存端点时执行一次,也会在每次投递前执行一次,所以如果某个域名后来开始解析到内网地址,系统就不会再向它投递。
请求
每次投递都是一个带 JSON 请求体的 POST。
Headers
| Header | Example | Meaning |
|---|---|---|
Content-Type | application/json | 始终是 JSON |
User-Agent | TicketWave-Webhooks/1.0.5 | 发送它的 bot 版本 |
X-TicketWave-Event | ticket.created | 事件类型 |
X-TicketWave-Delivery | wh_3f2a… | 这次投递的唯一 id |
X-TicketWave-Timestamp | 1786224191 | Unix 秒数,签名的一部分 |
X-TicketWave-Signature | sha256=9f86d0… | 请求的 HMAC |
Body
每个载荷都使用相同的信封结构。只有 data 会因事件类型而不同:
{
"event": "ticket.created",
"timestamp": "2026-08-26T08:23:11.000Z",
"guild_id": "123456789012345678",
"data": {
"ticket_id": "ticket-1042",
"ticket_num_id": 1042,
"channel_id": "998877665544332211",
"category": "🤖 Support",
"user": { "id": "987654321098765432", "username": "Luna" },
"created_at": "2026-08-26T08:23:11.000Z"
}
}每种类型的 data 对象请参阅 Event Reference。
验证签名
任何发现你端点 URL 的人都可以向它发送 POST 请求。签名就是你用来区分真实 TicketWave 投递和伪造请求的方式。
一定要验证。 一个未验证的端点如果会在你的系统里创建或关闭东西,那就是敞开的后门。
签名是如何生成的
TicketWave 会把时间戳和 原始 请求体用一个点连接起来,然后使用你的端点密钥对结果执行 HMAC-SHA256:
signed_payload = X-TicketWave-Timestamp + "." + raw_request_body
signature = HMAC_SHA256(signed_payload, your_endpoint_secret)请求头里携带的是这个摘要的十六进制编码,并带有前缀:sha256=<digest>。
你的接收端必须做什么
读取原始请求体。 要用你收到的精确字节来验证。如果你的框架先解析 JSON,再重新序列化,键的顺序或空格可能会变化,摘要就会不匹配。
重新计算 HMAC,使用你的密钥对 timestamp + "." + rawBody 进行计算。
使用常量时间比较(Node 里用 crypto.timingSafeEqual)。普通的 === 会泄露时序信息。
检查时间戳是否足够新——通常默认允许五分钟偏差就很好。时间戳包含在签名载荷里,所以攻击者不能用一个旧请求配上新的时间戳来重放。
完整实现请见 Example Server 页面。
重试
失败的投递会自动重试。
| Attempts | 3(第一次尝试加上 2 次重试) |
| Backoff | 1 秒,然后 5 秒 |
| Retried on | 网络错误、超时、408、429 以及任何 5xx |
| Not retried on | 其他所有 4xx —— 这些表示你的端点有意拒绝了请求 |
由于重试,你的端点可能会收到 同一个事件两次。请把 X-TicketWave-Delivery 当作幂等键:记住你已经处理过的 id,并忽略重复项。
投递 不会 保证顺序。如果两个工单同时创建,请求可能以任意顺序到达——如果顺序对你很重要,请使用请求体中的 timestamp 字段。
投递历史
控制台的 Webhooks 页面会列出每一次投递,以及它的状态、响应码、耗时和尝试次数。打开某一行即可查看发送的完整请求载荷,以及你的服务器返回的响应。
失败的投递可以在详情页通过 Retry Webhook 重新发送。它会把原始载荷再次发送到同一个端点,并记录一条新的投递记录。
历史记录会保留 30 天,之后会自动清理。
故障排查
| Problem | Fix |
|---|---|
| 无法保存端点 | URL 必须是 https://,并且能解析到公网地址 |
| 所有内容都显示失败,但没有响应码 | 请求根本没有到达你这里——超时、DNS 失败或连接被拒绝 |
| 签名始终不匹配 | 你哈希的是解析后的 body,而不是原始字节,或者忘了 timestamp + "." 前缀 |
| 过一段时间后投递停止了 | 检查你的主机是否开始返回 4xx——这些不会被重试 |
| 某个事件一直没到 | 端点没有订阅该类型,或者服务器失去了高级版 |
| 重复事件 | 重试时属于预期行为——请基于 X-TicketWave-Delivery 去重 |
下一步
- Event Reference(每个事件及其载荷)
- Example Server(一个可运行的 Express 接收端)
- Log Channels(相同的事件,但会发布到 Discord)
How is this guide?
