Webhook
Webhook 允许外部系统在 Kanera 中的工作变化时做出反应。
当另一个系统应该知道变更而不需要不断调用 API 时,使用 webhook。 常见示例包括:
- 更新外部仪表板。
- 同步更改的卡片到另一个系统。
- 触发部署或审查工作流程。
- 在审计系统中记录 Kanera 活动。
要将卡片活动发布到团队聊天通道中,您不需要自己的 webhook。 Kanera 内置了 聊天通知目标 为 Slack, Discord, Telegram, 并 Zulip 当你需要完整的事件目录、准确的有效载荷和签名验证时,可以使用 webhook。
工作区管理员可以从 工作区设置 -> API,在 自定义webhook 单独的板有相同的 API 集成还可以通过公共 API 或 TypeScript SDK 注册端点,并且可以在自身的板设置中注册端点。
每个 webhook 目标都可以接收所有事件或仅接收选定的事件类型。 Leave eventTypes 空接收每个事件,或提供一个或多个事件类型字符串如下所示。
计划和权限
- 工作区管理员可以查看和管理该工作区中的每个通用 Webhook 端点。
- 当端点的用户是工作区成员时,具有写入功能的 API 凭证或交互式 OAuth 代理可以在没有管理员权限的情况下注册端点。该端点的范围仅限于创建连接:只有相同的连接和工作区管理员才能查看或管理它。读取范围的凭据无法管理 Webhook。
- 网络钩子需要 Pro 30天 Pro试用。它们在托管 Free 上不可用。
- 在托管版 Free 套餐中,创建表单会替换为 您的计划中不提供 Webhook.
撤销凭据或代理连接还会删除其连接范围内的端点。 管理员创建的工作区端点保留,直到管理员删除它们。
降级时会发生什么
当托管组织从 Pro 或试用版迁移到 Free 时,Kanera 会暂停每个启用的 webhook 端点,而不是删除任何内容。 它还会撤销工作区 API 密钥(私钥仍然有效 ), 并禁用超出 Free 限制的自动化。
| 主机上 Free | 行为 |
|---|---|
| 现有终点 | 自动暂停,并且当组织处于 Free 状态时无法重新启用它们。 |
| 新活动 | 未到达您的端点。 |
| 还有什么正在等待发送 | 记录为失败 webhooks are unavailable on the current plan 而不是迟到。 |
| 交付失败 | 无法手动重试。 |
| 配置和历史记录 | 保留。 端点、事件选择、秘密和交付历史记录都仍然存在。 |
升级回 Pro 版本会重新启用因降级而暂停的端点,并恢复其撤销的工作区 API 密钥,因此无需重新构建恢复的集成。
通过编程注册端点
公共 API 会公开 Workspace 设置所使用的相同通用端点生命周期。
/api/v1/workspaces/{workspaceId}/webhooks
| 方法 | 路径 | 使用 |
|---|---|---|
GET | (基本路径) | 列出调用者可见的端点。 |
POST | (基本路径) | 创建一个端点并返回其签名秘密一次。 |
PATCH | /{endpointId} | 更改其名称、URL、事件过滤器或启用状态。 |
POST | /{endpointId}/secret | 旋转签名秘密。 |
DELETE | /{endpointId} | 删除端点。 |
GET | /{endpointId}/deliveries | 阅读最近的交付尝试。 |
POST | /{endpointId}/deliveries/{deliveryId}/retry | 重试一次失败的交付。 |
使用 TypeScript SDK,使用 kanera.webhookEndpoints.create(), .list(), .update(), .rotateSecret(), .deliveries(), .retryDelivery(), 和 .delete()。返回的端点有 scope: "workspace" 用于管理员管理的端点或 scope: "connection" 当非管理员凭据注册它时。 secret 通过创建或旋转立即返回;后续读取从不暴露它。
Webhook 事件类型
公共 API 将当前列表公开于:
https://api.kanera.app/webhook-event-types
| 面积 | 事件类型 |
|---|---|
| 列表 | list:created, list:updated, list:moved, list:rebalanced, list:deleted |
| 板隔板 | separator:created, separator:updated, separator:moved, separator:rebalanced, separator:deleted |
| 自定义字段 | customField:created, customField:updated, customField:moved, customField:rebalanced, customField:deleted |
| 自定义字段选项 | customFieldOption:created, customFieldOption:updated, customFieldOption:moved, customFieldOption:rebalanced, customFieldOption:deleted |
| 卡片 | card:created, card:updated, card:moved, card:rebalanced, card:deleted |
| 卡片自定义字段 | card:customFieldValue:set, card:customFieldValue:cleared |
| 卡片标签和受让人 | card:labels:set, card:assignees:set |
| 卡片附件 | card:attachment:created, card:attachment:deleted |
| 卡片清单 | card:checklist:created, card:checklist:updated, card:checklist:moved, card:checklist:rebalanced, card:checklist:deleted |
| 卡清单项目 | card:checklistItem:created, card:checklistItem:updated, card:checklistItem:moved, card:checklistItem:rebalanced, card:checklistItem:deleted |
| 工作区卡片标签 | cardLabel:created, cardLabel:updated, cardLabel:moved, cardLabel:rebalanced, cardLabel:deleted |
| 评论意见 | comment:created, comment:updated, comment:deleted |
| 评论反应 | comment:reaction:added, comment:reaction:removed |
| 卡片进给 | card:feedItem:created, card:feedItem:updated, card:feedItem:deleted |
| 看板 | board:created, board:updated, board:moved, board:rebalanced, board:deleted |
| 看板成员 | board:member:added, board:member:removed |
| 工作区 | workspace:updated, workspace:deleted |
| 工作区成员 | workspace:member:added, workspace:member:updated, workspace:member:removed |
| 用户 | user:profile:updated |
| 笔记 | note:created, note:updated, note:moved, note:rebalanced, note:deleted, note:locked, note:unlocked |
| 代理运行 | agentRun:started, agentRun:updated |
有效载荷格式
Kanera 将每个 webhook 作为 JSON 对象发送,具有稳定的信封和事件特定性 data.
| 外地 | 意义 |
|---|---|
id | 唯一的 webhook 事件 ID。 X-Kanera-Event-Id. |
type | 事件类型,如 card:created. |
workspaceId | 事件发生的工作区。 |
boardId | 当事件属于看板范围时,事件发生的看板。 |
cardId | 当事件有效负载识别出卡时,卡参与事件。 |
occurredAt | 事件发生时的 ISO 8601 时间戳。 |
data | 形状取决于: type 并匹配相应的实时事件有效载荷。 |
代理运行的事件携带 { boardId, cardId, run } 在 data, 其中 run 是什么原因导致了这种情况? agentName, 的 userId 它为什么会这样做? status, title, summary, externalUrl,及其 startedAt, heartbeatAt, 和 endedAt 时间戳。 agentRun:updated 对于每一个心跳以及状态变化都会发射,所以请将您的处理器键入 run.status 一个停止心跳的运行作为最后一个步骤到达,而不是将每个交付作为新的工作。 agentRun:updated 与 status: "stalled".
例 子 card:created 有效载荷:
{
"id": "7e5c982a-e6c9-4ea7-9647-b14fe73ed50a",
"type": "card:created",
"workspaceId": "a069fd45-3bb3-4928-bac9-cb574a050d20",
"boardId": "48a52a55-763e-4c64-8a76-51ac56247f5c",
"cardId": "70c39cef-1aec-44e6-a8f5-f36f765a818d",
"occurredAt": "2026-05-28T12:34:56.000Z",
"data": {
"boardId": "48a52a55-763e-4c64-8a76-51ac56247f5c",
"card": {
"id": "70c39cef-1aec-44e6-a8f5-f36f765a818d",
"boardId": "48a52a55-763e-4c64-8a76-51ac56247f5c",
"listId": "3bb3c1b3-8fc7-4807-854d-2d5ff62df147",
"title": "Prepare kickoff agenda",
"position": "1000.0000000000"
}
}
}
网络钩传送
Kanera 会以 JSON 形式发送 webhook 交付,并包含您可以用来验证请求的报头:
| 头条 | 意义 |
|---|---|
X-Kanera-Event-Id | 唯一事件 ID。 |
X-Kanera-Timestamp | Unix 时间戳(秒 ) 。 |
X-Kanera-Signature | sha256= 加上一个 HMAC-SHA256 签名。 |
签名是根据以下因素计算的:
${timestamp}.${rawBody}
使用 webhook 端点秘密。
当端点返回非 2xx 状态或无法访问时,Webhook 交付将重试。 Kanera 记录交付历史记录,以便您可以查看失败、重试交付、暂停目标和从工作区 API 设置中轮换秘密。
验证 webhook 签名
在信任有效载荷之前验证签名。
该 TypeScript SDK 为你做这件事,并且抛出而不是返回一个布尔值,这样处理器就不会忘记检查:
import { parseWebhook } from "@kanera/sdk";
app.post("/kanera/webhook", express.raw({ type: "application/json" }), async (req, res) => {
const event = await parseWebhook({
secret: process.env.KANERA_WEBHOOK_SECRET!,
payload: req.body, // the raw bytes
headers: req.headers,
});
// Handle event.type, event.workspaceId, event.boardId, event.cardId, and event.data.
res.sendStatus(204);
});
要自己验证,请比较以上的 HMAC {timestamp}.{body} 注意长度检查: crypto.timingSafeEqual 当两个缓冲区的长度不同时,攻击者会控制它们。
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/kanera/webhook", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.header("X-Kanera-Timestamp") ?? "";
const signature = req.header("X-Kanera-Signature") ?? "";
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.KANERA_WEBHOOK_SECRET!)
.update(`${timestamp}.${req.body.toString("utf8")}`)
.digest("hex");
const received = Buffer.from(signature);
const digest = Buffer.from(expected);
if (received.length !== digest.length || !crypto.timingSafeEqual(received, digest)) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body.toString("utf8"));
// Handle event.type, event.workspaceId, event.boardId, event.cardId, and event.data.
res.sendStatus(204);
});
始终根据原始请求正文进行验证。如果您的框架在验证之前解析 JSON,则签名检查可能会失败或变得不可靠。
Kanera也发送 X-Kanera-Timestamp 以及 X-Kanera-Event-Id。拒绝时间戳超出现在几分钟时间的交付以绑定重播,并使用事件 ID 使处理程序幂等,因为交付可能会重试。
相关出境选项
Webhook 是通用路径。 针对特定受众群体,有两种更狭窄的选项:
| 路径 | 观众 | 有效载荷 | 设置位置 | 订阅方案 |
|---|---|---|---|---|
| Webhook | 另一个系统 | 整个事件目录中已签名的 JSON | 工作区设置 -> API | Pro或试用 |
| 聊天目的地 | 团队频道 | 格式化的消息,适用于 Slack、Discord、Telegram 或 Zulip | 工作区设置 -> 集成 | Pro或试用 |
| 个人交付渠道 | 1人 | ntfy、Gotify 或已签名的 JSON 用于您自己的通知 | 个人资料设置 -> 通知 | Pro或试用 |
| MCP 活动订阅 | AI代理的主机 | 签名卡和评论发生到回调代理人验证 | 代理通过 MCP 订阅;无需在 Kanera 中设置 | 每个计划 |
聊天目标与 Webhook 端点分开管理,并且不会出现在 Webhook 列表中,因此轮换 Webhook 机密绝不会影响聊天目标。
自动化的 调用网络钩子 行动是有针对性的,而不是基于订阅。 automation:webhook:called 只对规则中选择的端点执行,而不管该端点的普通 eventTypes 过滤器,同时保持相同的签名、重试和递送历史记录行为。 自动化.
