Webhooks
Webhooks 让您的系统在队列事件发生时接收实时 HTTP POST 通知 — 排队号加入、 被叫号、完成,等等。
- 打开 admin.jonot.io/settings/integrations。
- 点击添加端点。
- 输入您的服务器监听的公共 HTTPS URL。
- 选择要订阅的事件。
- 点击保存。
保存好您的签名密钥 — 它只显示一次。要验证端点可达,使用端点编辑页面上的 发送测试请求按钮。
| 事件名称 | 触发时机 |
|---|---|
ticket.joined | 顾客加入队列 |
ticket.called | 员工将排队号叫到服务台 |
ticket.completed | 排队号被标记为完成 |
ticket.cancelled | 顾客或员工取消排队号 |
ticket.skipped | 排队号被跳过(可召回的暂缓) |
ticket.no_show | 已叫号或被跳过的排队号被确认为未到场 |
queue.status_changed | 队列状态变更(ACTIVE、PAUSED 或 CLOSED) |
每次投递都是一个 HTTP POST,带有:
Content-Type: application/jsonX-Jonot-Event: ticket.calledX-Jonot-Delivery-Id: <uuid>X-Jonot-Timestamp: 2024-06-01T12:00:00.000ZX-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 秒 |
轮换签名密钥
Section titled “轮换签名密钥”- 打开端点编辑页面。
- 点击轮换签名密钥。
- 在对话框中确认轮换。
- 立即复制新密钥 — 它只显示一次,无法找回。如果没保存就关闭对话框,您必须再次轮换才能获得新的明文。
- 更新您的服务器,用新密钥验证签名。
没有重叠窗口。 您确认轮换的那一刻,旧密钥立即停止验证。计划在轮换后 立即将新密钥部署到接收方。
UI 在轮换按钮旁边显示 当前密钥:····XXXX(最后四个字符),让您确认当前
生效的是哪个密钥 — 在轮换后确认接收方和服务器同步时很有用。
签名密钥卫生
Section titled “签名密钥卫生”何时轮换:
- 疑似泄露:密钥出现在日志文件中、与离职员工共享过,或被屏幕录制捕获。
- 例行卫生:定期轮换可限制未被发现的暴露的爆炸半径。
- 任何能读取密钥的服务发生变更后(密钥管理轮换)。
如何更新接收方:
- 在管理 UI 中轮换并复制新密钥。
- 更新接收方密钥存储中的密钥(环境变量、密钥管理器等)。
- 部署更新后的接收方。
- 在投递记录标签页确认下一次投递成功。
如果在保存前丢失了新密钥:
再次轮换。每次轮换生成新的随机密钥。之前的明文无法找回 — 服务器端只存储 AEAD 加密形式。