跳转到内容

Webhooks

Webhooks 让您的系统在队列事件发生时接收实时 HTTP POST 通知 — 排队号加入、 被叫号、完成,等等。

  1. 打开 admin.jonot.io/settings/integrations
  2. 点击添加端点
  3. 输入您的服务器监听的公共 HTTPS URL。
  4. 选择要订阅的事件。
  5. 点击保存

保存好您的签名密钥 — 它只显示一次。要验证端点可达,使用端点编辑页面上的 发送测试请求按钮。

事件名称触发时机
ticket.joined顾客加入队列
ticket.called员工将排队号叫到服务台
ticket.completed排队号被标记为完成
ticket.cancelled顾客或员工取消排队号
ticket.skipped排队号被跳过(可召回的暂缓)
ticket.no_show已叫号或被跳过的排队号被确认为未到场
queue.status_changed队列状态变更(ACTIVEPAUSEDCLOSED

每次投递都是一个 HTTP POST,带有:

Content-Type: application/json
X-Jonot-Event: ticket.called
X-Jonot-Delivery-Id: <uuid>
X-Jonot-Timestamp: 2024-06-01T12:00:00.000Z
X-Jonot-Signature: v1,<base64-hmac-sha256>
X-Jonot-Payload-Version: v1

默认正文是 JSON 封装。payload 字段的形状因事件而异:

ticket.joined

{
"deliveryId": "<uuid>",
"event": "ticket.joined",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"ticket": { "id": "tkt_…", "queueId": "q_…" },
"queue": { "id": "q_…", "name": "Main Queue" },
"location": { "id": "loc_…", "name": "Downtown" }
}
}

ticket.called / ticket.completed / ticket.skipped / ticket.no_show

{
"deliveryId": "<uuid>",
"event": "ticket.called",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"ticket": {
"id": "tkt_…",
"number": 42,
"status": "CALLED",
"queueId": "q_…",
"createdAt": "2024-06-01T11:58:00.000Z",
"updatedAt": "2024-06-01T12:00:00.000Z"
}
}
}

ticket.cancelled

{
"deliveryId": "<uuid>",
"event": "ticket.cancelled",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"ticketId": "tkt_…",
"queueId": "q_…"
}
}

queue.status_changed

{
"deliveryId": "<uuid>",
"event": "queue.status_changed",
"timestamp": "2024-06-01T12:00:00.000Z",
"org": { "id": "org_…", "name": "My Org" },
"payload": {
"queueId": "q_…",
"status": "PAUSED"
}
}

Jonot 对以下字符串使用 HMAC-SHA256 为每次投递签名:

<deliveryId>.<timestamp>.<body>

在 Node.js(≥18)中验证:

import { createHmac, timingSafeEqual } from "node:crypto";
/**
* Returns true when the signature header is valid and the timestamp is
* within 5 minutes of now. Throws for malformed input.
*/
function verifySignature(secret, deliveryId, timestamp, body, header) {
// Replay-attack guard: reject deliveries older than 5 minutes.
const ageMs = Date.now() - new Date(timestamp).getTime();
if (Math.abs(ageMs) > 5 * 60 * 1000) return false;
const expected =
"v1," +
createHmac("sha256", secret)
.update(`${deliveryId}.${timestamp}.${body}`)
.digest("base64");
// timingSafeEqual prevents timing-oracle attacks.
// Buffers must be the same length — if lengths differ the signature is
// invalid, but we still compare a dummy value to keep constant time.
const expectedBuf = Buffer.from(expected);
const headerBuf = Buffer.from(header);
if (expectedBuf.length !== headerBuf.length) return false;
return timingSafeEqual(expectedBuf, headerBuf);
}

您可以将默认 JSON 正文替换为自定义模板。模板使用 Mustache 精简语法:

  • {{ path.to.value }} — 被替换并按内容类型转义:application/json 为 JSON 字符串转义,application/x-www-form-urlencoded 为百分号编码,text/plain 为原样
  • {{{ path.to.value }}} — 原样替换(不转义)

application/json 的模板示例(订阅了 ticket.joined):

{
"type": "{{ event }}",
"ticketId": "{{ payload.ticket.id }}",
"queueName": "{{ payload.queue.name }}"
}

载荷模板字段是一个完整的代码编辑器,具有:

  • 针对 JSON 模板的语法高亮和括号匹配
  • 变量面板 — 一行始终可见的插入按钮,每个可用变量路径一个(针对您选择的事件)。点击按钮在光标处插入 {{ path }} 令牌。
  • 实时验证 — 输入时,编辑器同时检查 JSON 结构(当内容类型为 application/json 且没有原始 {{{ }}} 标签时)和变量路径。诊断结果以编辑器内联标记和其下方的摘要横幅显示:
    • 错误 — 在您选择的所有事件中都不存在的路径。
    • 警告 — 仅存在于部分所选事件中的路径(其他事件的投递中将为空)。

验证在保存时也在服务器端运行 — 编辑器反馈与服务器规则完全一致。

失败的投递(非 2xx 或连接错误)由 Cloudflare Queues 以指数退避重试最多 3 次。3 次失败后,投递进入死信队列,端点的连续失败计数器递增。

端点累计20 次连续失败后自动停用。从端点编辑页面重新启用;计数器重置 为零。

每个端点的投递记录标签页显示最近 30 天的投递尝试:事件类型、HTTP 状态、 尝试次数和时间戳。使用加载更多按钮翻页查看更早的记录。

限制
每个组织的端点数5
每个端点的自定义请求头数10
请求头值长度1 024 字节
载荷模板大小16 KB
投递超时10 秒
投递历史保留期30 天
每个组织的最大投递速率120 次 / 60 秒
  1. 打开端点编辑页面。
  2. 点击轮换签名密钥
  3. 在对话框中确认轮换。
  4. 立即复制新密钥 — 它只显示一次,无法找回。如果没保存就关闭对话框,您必须再次轮换才能获得新的明文。
  5. 更新您的服务器,用新密钥验证签名。

没有重叠窗口。 您确认轮换的那一刻,旧密钥立即停止验证。计划在轮换后 立即将新密钥部署到接收方。

UI 在轮换按钮旁边显示 当前密钥:····XXXX(最后四个字符),让您确认当前 生效的是哪个密钥 — 在轮换后确认接收方和服务器同步时很有用。

何时轮换:

  • 疑似泄露:密钥出现在日志文件中、与离职员工共享过,或被屏幕录制捕获。
  • 例行卫生:定期轮换可限制未被发现的暴露的爆炸半径。
  • 任何能读取密钥的服务发生变更后(密钥管理轮换)。

如何更新接收方:

  1. 在管理 UI 中轮换并复制新密钥。
  2. 更新接收方密钥存储中的密钥(环境变量、密钥管理器等)。
  3. 部署更新后的接收方。
  4. 投递记录标签页确认下一次投递成功。

如果在保存前丢失了新密钥:

再次轮换。每次轮换生成新的随机密钥。之前的明文无法找回 — 服务器端只存储 AEAD 加密形式。