Webhook
仅 Cloud
Webhook 仅在 Cloud 版本中可用。
Webhook 让 ADHDev Cloud 把机器和代理事件推送到你自己的 HTTP 端点。
当前重要行为:
- webhook API 在线
- 专用的
/webhooks仪表板页面目前不是一个稳定的标准导航界面 - 请使用 REST API 参考作为负载、事件和投递历史结构的权威契约
- webhook 的创建 / 切换 / 删除 / 测试操作是受近期登录保护的云账户操作,需要一个新鲜的仪表板会话 JWT,而非 API 密钥
创建 Webhook
从当前已登录的云端仪表板会话中创建 webhook。底层请求体为:
{
"url": "https://example.com/hooks/adhdev",
"events": ["agent:status", "agent:approve_request"]
}如果你从一个陈旧的浏览器会话或用 API 密钥调用变更类 webhook 端点,服务器会返回 403 RECENT_LOGIN_REQUIRED 而不是配置该 hook。
创建响应返回:
- webhook 记录
- 一个只显示一次的
secret,用于签名验证
events 是一个允许列表,而不是自由列出的名称:只接受下面列出的名称(以及 "*"),其他名称会被静默丢弃。使用 ["*"] 可订阅服务器发出的所有事件,包括那些无法单独订阅的更细粒度事件。
事件
可单独订阅(events 中被接受):
| 事件 | 触发条件 |
|---|---|
daemon:connect | 守护进程连接到服务器 |
daemon:disconnect | 守护进程连接丢失 |
agent:status | 代理状态变化(限流,每个守护进程每 10 秒最多 1 次) |
agent:approve_request | 保留字段 —— 在允许列表中,但服务器目前不会发出 |
服务器会发出,但仅投递给用 "*" 订阅的 webhook(不可单独订阅):
| 事件 | 触发条件 |
|---|---|
agent:generating_started | 代理开始生成响应 |
agent:generating_completed | 代理完成生成 |
agent:waiting_approval | 代理正在等待批准 |
agent:waiting_choice | 代理正在等待多选提示的选择 |
agent:stopped | 代理会话已停止 |
monitor:no_progress | 代理看起来已停滞、没有进展(旧别名:monitor:long_generating) |
webhook:test | 通过 POST /webhooks/{id}/test 显式请求的测试投递 —— 完全绕过订阅过滤,仅投递给被测试的那个 webhook |
负载格式
每次投递都是一个 JSON 正文,包含三个顶层字段:event、payload 和 timestamp(服务器发出这次投递的时间,epoch 毫秒)。对于代理和监控事件(除 agent:status 以外的 agent:*,以及 monitor:no_progress),负载如下所示:
{
"event": "agent:generating_completed",
"payload": {
"daemonId": "8e1f4c2a9b7d3e5f6a0c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f",
"daemonType": "adhdev-daemon",
"event": "agent:generating_completed",
"timestamp": 1714000000000,
"targetSessionId": "9b2d7c4e-1f3a-4b5c-8d6e-0a1b2c3d4e5f",
"providerType": "claude-cli",
"providerSessionId": "3f2a9c1e-5b7d-4e8a-9c0f-1a2b3c4d5e6f",
"workspaceName": "/home/dev/projects/myproject",
"duration": 42,
"surfaceHidden": false,
"muted": false
},
"timestamp": 1714000000215
}| 字段 | 出现条件 |
|---|---|
daemonId | 始终 — 上报该事件的守护进程连接 |
event, timestamp | 始终 — 事件名称,以及守护进程观测到它的时间(epoch 毫秒) |
targetSessionId | 事件针对某个代理会话时 — agent:generating_completed 和 agent:stopped 始终包含 |
providerType | 守护进程能解析到会话时出现。代理提供方(例如 claude-cli、cursor)。无法解析时不含此字段,且不会填入守护进程类型 |
daemonType | 始终。发送该事件的守护进程类型,目前为 adhdev-daemon |
providerSessionId | 提供方公开了自己的会话 ID 时 |
workspaceName | 会话有工作区时 — CLI 代理的工作目录,或 IDE 的工作区 |
duration | 仅 agent:generating_completed,且守护进程观测到了本轮开始时 — 整数秒 |
elapsedSec | monitor:no_progress — 没有可见进展的秒数 |
modalMessage, modalButtons | agent:waiting_approval / agent:waiting_choice — 审批提示文本及按钮标签 |
surfaceHidden, muted | 守护进程托管该会话时 — 它在仪表盘上是否被隐藏或静音 |
daemon:connect、daemon:disconnect、agent:status 和 webhook:test 各自使用不同形状的负载。除 modalMessage 中的审批提示文本外,负载中绝不包含聊天消息、聊天标题或代理回复。
签名验证
每个 webhook 请求包含:
X-ADHDev-SignatureX-ADHDev-Event
签名格式为:
X-ADHDev-Signature: t=1714000000000,v1=a3f9c2b1...请针对原始请求体进行验证。
import crypto from 'crypto'
function verifySignature(body, signature, secret) {
const parts = Object.fromEntries(signature.split(',').map((p) => p.split('=')))
const timestamp = parts.t
const received = parts.v1
const payload = `${timestamp}.${body}`
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(received),
Buffer.from(expected)
)
}
// Express 示例
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-adhdev-signature']
const body = req.body.toString('utf8')
if (!verifySignature(body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(body)
// 处理事件...
res.sendStatus(200)
})import hmac
import hashlib
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in signature.split(','))
timestamp = parts.get('t', '')
received = parts.get('v1', '')
payload = f"{timestamp}.{body.decode('utf-8')}".encode()
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(received, expected)
# Flask 示例
@app.route('/webhook', methods=['POST'])
def webhook():
sig = request.headers.get('X-ADHDev-Signature', '')
if not verify_signature(request.data, sig, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
# 处理事件...
return '', 200你的 webhook secret 在你创建 webhook 时只显示一次 —— 请妥善保管。
管理端点
GET /api/v1/webhooks
POST /api/v1/webhooks
PATCH /api/v1/webhooks/{webhookId}
DELETE /api/v1/webhooks/{webhookId}
GET /api/v1/webhooks/{webhookId}/deliveries?limit=20
POST /api/v1/webhooks/{webhookId}/testGET 端点是最安全的自动化界面。变更类端点(POST、PATCH、DELETE、test)是受近期登录保护的仪表板会话操作。
重试策略
失败的投递会以指数退避方式最多重试 3 次。
套餐限制
| 套餐 | 最大 Webhook 数 |
|---|---|
| Free | 2 |
| Pro | 10 |
| Ultra | 50 |
| Enterprise | 无限 |
