文档 / 出站 Webhook
出站 Webhook
出站 Webhook 是指当托管账户发生某件事时,面板发往您的某个地址的一条消息,这样另一个系统就可以作出反应:您自己的计费系统、聊天频道、电子表格、开通脚本。
Webhook 面向 管理员 和 经销商。管理员会收到所有账户的通知。经销商只会收到属于该经销商的账户的通知。客户和 API 令牌无法创建它们:Webhook 在面板本身中、在个人自己的会话里设置,这样一个泄露的令牌就永远不会变成面板事件的永久数据流。
添加一个
打开您的账户安全页面(管理员:Security;经销商:Login Security),在 API 令牌下方找到 Webhooks。
- 输入地址,例如
https://example.com/hooks/panel。 - 勾选您想要的事件。
- 按 Add webhook。
面板会 只显示一次 一个 签名密钥(以 whsec_ 开头)。请现在就复制。它被加密存储,无法再次显示;如果您丢失了它,请删除该 Webhook 并重新添加一个。
每个人最多可以有 10 个 Webhook。
地址
- 必须以
https://开头。普通的http://会被拒绝,因为调用中带有账户详情。 - 不得包含用户名或密码。请改用签名校验。
- 最多 500 个字符。
- 必须位于公共互联网上。位于本服务器上、私有网络中、回环接口上或云服务商元数据范围内的地址都会被拒绝,无论是在您保存地址时,还是在面板连接的那一刻(因此一个后来指向私有地址的主机名同样行不通)。
- 不会跟随重定向。请填写最终地址。
事件
| 事件 | 发送时机 |
|---|---|
account.created |
托管账户被创建 |
account.suspended |
账户被暂停 |
account.unsuspended |
被暂停的账户被恢复 |
account.deleted |
账户被删除 |
account.activity |
账户上的其他活动 |
每个 Webhook 上的 Test 按钮会发送一个名为 ping 的事件,让您看到您的地址是否有响应。同一时间只能有一个测试在等待。
发送的内容
每次调用都是带 JSON 正文的 HTTP POST:
{
"event": "account.created",
"at": "2026-10-23T08:15:00Z",
"account": { "id": 42, "username": "example", "domain": "example.com" },
"detail": "short text about what happened"
}
at 是 UTC 时间。ping 事件的形式为 {"event":"ping","at":"…","webhook_id":7}。
每次调用都会附带以下标头:
| 标头 | 含义 |
|---|---|
X-Nimbopanel-Event |
事件名称,例如 account.created |
X-Nimbopanel-Delivery |
标识这次投递的编号。同一次投递的重试保持相同编号,因此可用它来忽略重复 |
X-Nimbopanel-Timestamp |
调用被签名的时间,以 Unix 秒表示 |
X-Nimbopanel-Signature |
sha256= 后接签名(见下文) |
Content-Type |
application/json |
User-Agent |
Nimbopanel-Webhook/1 |
校验签名
任何人都可以向您的地址发送请求。签名就是您得知一次调用来自您的面板的方法。
签名是 HMAC-SHA256,以十六进制表示,输入为时间戳、一个点号,以及 原样接收的原始正文,使用您的签名密钥计算:
X-Nimbopanel-Signature == "sha256=" + hex( HMAC_SHA256( secret, X-Nimbopanel-Timestamp + "." + body ) )
请按以下顺序校验:
- 根据原始请求正文(在任何 JSON 解析或重新格式化之前)计算上面的值。
- 用 恒定时间 比较方式把它与标头比对。
- 如果时间戳与您的时钟相差超过 5 分钟,就拒绝该调用。这能防止有人重放旧的调用。
- 用
X-Nimbopanel-Delivery忽略您已经处理过的投递。
PHP 示例:
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_NIMBOPANEL_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_NIMBOPANEL_SIGNATURE'] ?? '';
$want = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!hash_equals($want, $sig) || abs(time() - (int) $ts) > 300) {
http_response_code(401);
exit;
}
Node.js 示例:
const crypto = require('crypto');
function valid(rawBody, headers, secret) {
const ts = headers['x-nimbopanel-timestamp'] || '';
const want = 'sha256=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');
const got = headers['x-nimbopanel-signature'] || '';
return got.length === want.length
&& crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want))
&& Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
}
响应、重试与暂停
- 请在 10 秒 内用任意 2xx 状态码作答。其他任何情况都算作失败。
- 失败的调用会在 1 分钟 后重试,然后是 5 分钟、30 分钟 和 2 小时 后。总共五次尝试,分布在大约两个半小时内,之后该投递被标记为失败。
- 发往同一地址的调用逐个发送,最早的先发。等待重试的投递不会拦住较新的投递,所以不要指望事件严格按顺序到达。一个缓慢的地址只会拖住它自己。
- 连续 50 次投递失败 后,该 Webhook 会 暂停:不再为它排入新的事件。一次成功的 Test 会清除计数,事件恢复流动。
- 一个 Webhook 最多有 200 个未投递的调用在等待。超过之后,在有些调用被投递或失败之前,它的后续事件不会再排队。
- 面板为每个 Webhook 保留最新的 50 次已完成的投递,以及它得到的响应,让您看清失败的原因。最近的几次会列在该 Webhook 下方。
如果面板显示的是原因而不是状态码,那么原因是以下之一:地址不在公共互联网上、10 秒内没有响应、主机名不存在、HTTPS 证书无效,或面板无法连接。
好习惯
- 用
200快速响应,之后再做真正的工作。接收方耗时超过 10 秒会被视为失败,并会再次收到同一事件。 - 对同一次投递处理两遍也不要出问题。重试和网络错误都可能产生重复。
- 不要把密钥放进您的代码仓库。如果它泄露了,请删除该 Webhook 并重新添加一个。