跳转到内容

Webhooks

OpenPR 使用 Webhook 实时通知外部系统状态变更。每次 Webhook 投递都使用 HMAC-SHA256 签名,记录在 webhook_deliveries 表中以供审计,并包含丰富的上下文数据供下游自动化使用。

  1. 发生状态变更(Issue 创建、Sprint 启动、提案提交等)
  2. OpenPR 查询 webhooks 表中工作区内订阅了该事件类型的活跃 Webhook
  3. 为每个匹配的 Webhook 构建包含完整实体数据的载荷
  4. 使用 Webhook 的密钥通过 HMAC-SHA256 对载荷签名
  5. 向 Webhook URL 发送带签名头的 HTTP POST 请求
  6. webhook_deliveries 中记录投递结果(状态、响应体、耗时)

OpenPR 触发 30 种事件类型,分为七个类别。

事件触发时机
issue.created创建新 Issue
issue.updatedIssue 字段被修改(包含 changes 差异)
issue.assignedIssue 指派人变更(包含旧/新指派人 ID)
issue.state_changedIssue 状态变更(包含旧/新状态)
issue.deletedIssue 被删除
事件触发时机
comment.createdIssue 新增评论(包含 mentions 数组)
comment.updated评论被编辑
comment.deleted评论被删除
事件触发时机
label.added标签被添加到 Issue
label.removed标签从 Issue 移除
事件触发时机
sprint.startedSprint 状态变为活跃
sprint.completedSprint 标记为已完成
事件触发时机
project.created创建新项目
project.updated项目字段被修改
project.deleted项目被删除
member.added用户加入工作区
member.removed用户从工作区移除
事件触发时机
proposal.created新提案被起草
proposal.updated提案字段被修改
proposal.deleted提案被删除
proposal.submitted提案提交审核
proposal.voting_started提案开始投票
proposal.archived提案被归档
proposal.vote_cast对提案进行投票
veto.exercised否决权人行使否决权
veto.withdrawn否决权被撤回
事件触发时机
escalation.started升级流程开始
appeal.created对决策提起申诉
事件触发时机
governance_config.updated治理配置被更改
事件触发时机
ai.task_completedAI 任务成功完成
ai.task_failedAI 任务在重试耗尽后失败

Webhook 通过 API 按工作区配置。每个 Webhook 指定:

字段类型说明
urlstring接收事件的 HTTPS 端点
secretstringHMAC-SHA256 签名的共享密钥
eventsJSONB订阅的事件类型数组
activeboolean启用或禁用该 Webhook
bot_user_idUUID(可选)设置后启用机器人上下文增强,用于 AI 任务派发

当设置了 bot_user_id 且事件涉及分配给该机器人的 Issue 时,载荷中包含带有代理派发信息的 bot_context 对象。

每次 Webhook 投递包含以下 HTTP 头:

Content-Typeapplication/json
User-AgentOpenPR-Webhook/1.0
X-Webhook-Signaturesha256=<十六进制编码的 HMAC>
X-Webhook-Event事件类型(如 issue.created
X-Webhook-Delivery唯一投递 UUID
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"event": "issue.created",
"timestamp": "2026-03-18T10:30:00.000Z",
"workspace": {
"id": "workspace-uuid",
"name": "My Workspace"
},
"project": {
"id": "project-uuid",
"name": "Backend API",
"key": "IM01"
},
"actor": {
"id": "user-uuid",
"name": "Admin",
"email": "admin@example.com",
"entity_type": "human"
},
"data": {
"issue": {
"id": "issue-uuid",
"key": "IM01-A1B2C3D4",
"title": "Fix authentication flow",
"description": "The login endpoint returns 500...",
"state": "todo",
"priority": "high",
"assignee_ids": ["bot-uuid"],
"label_ids": ["label-uuid"],
"sprint_id": "sprint-uuid",
"created_at": "2026-03-18T10:30:00.000Z",
"updated_at": "2026-03-18T10:30:00.000Z"
}
},
"bot_context": {
"is_bot_task": true,
"bot_id": "bot-uuid",
"bot_name": "Claude Agent",
"bot_agent_type": "claude-code",
"trigger_reason": "created",
"webhook_id": "webhook-uuid"
}
}

bot_context 字段仅在以下情况存在:

  1. Webhook 配置了 bot_user_id,且
  2. Issue 分配给了该机器人用户,或
  3. 机器人在评论中被 @提及

trigger_reason 字段指示机器人被触发的原因:

原因时机
createdIssue 创建时机器人为指派人
assignedIssue 被分配或更新时机器人为指派人
status_changedIssue 状态变更
mentioned机器人在评论中被 @提及
completedAI 任务完成
failedAI 任务失败

对于 issue.updatedissue.assignedissue.state_changed 事件,data 对象包含一个 changes 字段显示变更内容:

{
"data": {
"issue": { "..." : "..." },
"changes": {
"state": {
"old": "todo",
"new": "in_progress"
}
}
}
}

对于 comment.created 事件,data 包含一个用户 UUID 的 mentions 数组:

{
"data": {
"comment": { "..." : "..." },
"issue": { "..." : "..." },
"mentions": ["user-uuid-1", "user-uuid-2"]
}
}

要验证 Webhook 投递,请使用你的 Webhook 密钥计算原始请求体的 HMAC-SHA256,并与 X-Webhook-Signature 头中的签名进行比较。

import hmac
import hashlib
def verify_webhook(secret: str, body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
const crypto = require("crypto");
function verifyWebhook(secret, body, signatureHeader) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}

每次 Webhook 投递都持久化到 webhook_deliveries 表,包含:

  • 投递 UUID
  • Webhook ID
  • 事件类型
  • 完整载荷(JSONB)
  • 请求头
  • 响应状态码
  • 响应体
  • 错误消息(如投递失败)
  • 耗时(毫秒)
  • 成功标志
  • 时间戳

每次投递尝试后,Webhook 的 last_triggered_at 时间戳会被更新。

  • 超时:每次投递尝试 10 秒
  • 无自动重试:失败的投递会被记录但不会重试(AI 任务系统有自己的重试机制)
  • 异步派发:Webhook 在后台 Tokio 任务中触发,不会阻塞 API 响应
  • 尽力而为:如果载荷构建失败,错误会被记录并跳过该 Webhook