Skip to content

Webhook ​

仅 Cloud

Webhook 仅在 Cloud 版本中可用。

Webhook 让 ADHDev Cloud 把机器和代理事件推送到你自己的 HTTP 端点。

当前重要行为:

  • webhook API 在线
  • 专用的 /webhooks 仪表板页面目前不是一个稳定的标准导航界面
  • 请使用 REST API 参考作为负载、事件和投递历史结构的权威契约
  • webhook 的创建 / 切换 / 删除 / 测试操作是受近期登录保护的云账户操作,需要一个新鲜的仪表板会话 JWT,而非 API 密钥

创建 Webhook ​

从当前已登录的云端仪表板会话中创建 webhook。底层请求体为:

json
{
  "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),负载如下所示:

json
{
  "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,且守护进程观测到了本轮开始时 — 整数秒
elapsedSecmonitor:no_progress — 没有可见进展的秒数
modalMessage, modalButtonsagent:waiting_approval / agent:waiting_choice — 审批提示文本及按钮标签
surfaceHidden, muted守护进程托管该会话时 — 它在仪表盘上是否被隐藏或静音

daemon:connect、daemon:disconnect、agent:status 和 webhook:test 各自使用不同形状的负载。除 modalMessage 中的审批提示文本外,负载中绝不包含聊天消息、聊天标题或代理回复。

签名验证 ​

每个 webhook 请求包含:

  • X-ADHDev-Signature
  • X-ADHDev-Event

签名格式为:

text
X-ADHDev-Signature: t=1714000000000,v1=a3f9c2b1...

请针对原始请求体进行验证。

js
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)
})
python
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 时只显示一次 —— 请妥善保管。

管理端点 ​

text
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}/test

GET 端点是最安全的自动化界面。变更类端点(POST、PATCH、DELETE、test)是受近期登录保护的仪表板会话操作。

重试策略 ​

失败的投递会以指数退避方式最多重试 3 次。

套餐限制 ​

套餐最大 Webhook 数
Free2
Pro10
Ultra50
Enterprise无限

托管云端文档在此。开源与自托管文档位于 OSS 仓库。