跳转到内容

Webhooks

排队事件发生时,Webhook 会向您的系统发送 HTTP POST 请求。例如,顾客加入排队、工作人员叫号或完成服务时都可以触发请求。

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

签名密钥出现时,请立即复制并妥善保存。Jonot 只显示一次。您可以在端点编辑页面点击发送测试请求,检查 Jonot 能否访问端点。

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

管理员删除门店或队列时,Jonot 会为每个仍在等待或已被叫号的排队号发送 ticket.cancelled,每次删除最多 50 个排队号。超出上限的排队号不会产生事件。删除组织不会发送任何 Webhook,因为其端点会随组织一起删除。

每次投递都是一个 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 }} 令牌。
  • 实时验证 — 输入时,编辑器会检查变量路径。如果内容类型是 application/json,且模板不包含原始 {{{ }}} 标签,还会检查 JSON 结构。问题会在代码中标记,并列在编辑器下方:
    • 错误 — 在您选择的所有事件中都不存在的路径。
    • 警告 — 仅存在于部分所选事件中的路径(其他事件的投递中将为空)。

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

端点返回非 2xx 状态或出现连接错误时,Cloudflare Queues 最多重试 3 次,每次重试前等待更长时间。如果初次投递和 3 次重试都失败,投递会进入死信队列,端点的连续失败次数加一。

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

如果某个事件的投递会超出组织上限,该事件的全部投递都会记录为超出速率限制,且一条都不会发送。Jonot 不会重试,也不会计入自动停用端点的 20 次连续失败。投递记录标签页按每分钟上限记录被丢弃的投递,因此长时间的流量高峰只会留下有限数量的记录,而不是每条被丢弃的投递一条记录。

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

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

新旧密钥不能同时使用。 确认轮换后,旧密钥立即失效。请马上更新接收 Webhook 的系统。

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

何时轮换:

  • 疑似泄露:密钥出现在日志文件中、与离职员工共享过,或被屏幕录制捕获。
  • 定期轮换:定期更换密钥可以缩短泄露密钥的可用时间。
  • 任何能读取密钥的服务发生变更后(密钥管理轮换)。

如何更新接收方:

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

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

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