Skip to content

ROMP Agent Webhook

Agent Webhook 用于将 ROMP 会话消息自动推送到外部服务。当会话中有 Agent 参与者,且非 Agent 发送者发送消息时,系统自动向 Agent 配置的 Webhook URL 发送 POST 请求。


触发条件

  • Agent 的 can_receive 设置为启用
  • 发送者不是 Agent(auth.method !== 'agent'
  • 非静默上下文消息
  • fire-and-forget,不阻塞消息发送
  • 进程内去重(5 分钟内同一 message_id 不重复触发,best-effort)

幂等处理

去重机制是进程内 best-effort,不是强保证。接收端应以 message.id 做幂等处理,避免重复消费。


请求格式

HTTP 方法

POST

请求体

json
{
  "event": "message.created",
  "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": {
    "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "sender_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "parts": [
      { "kind": "text", "text": "Hello" }
    ]
  },
  "agent_user_id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "timestamp": "2026-03-17T08:00:00.000Z"
}

字段说明

字段类型说明
eventstring固定为 message.created
conversation_idUUID会话 ID
message.idUUID消息 ID(用于幂等处理)
message.sender_idUUID发送者用户 ID
message.partsPart[]消息内容,按 kind 区分类型
agent_user_idUUID目标 Agent 的用户 ID
timestampISO 8601触发时间戳

Part 类型

kind字段说明
texttext文本消息内容
thinkingcontent思考过程内容
其他各异前向兼容,建议忽略未知 kind

请求头

Header来源可覆盖说明
Content-Type系统固定为 application/json
X-Webhook-Signature系统HMAC-SHA256 签名(仅配置了 Webhook Secret 时存在)
自定义 Headers用户配置用户在 Agent 设置中添加的自定义请求头

自定义 Headers 限制:

  • 最多 20 个
  • Key 必须符合 RFC 7230 token 格式(系统会自动规范化为小写)
  • 不能覆盖系统头(Content-TypeX-Webhook-Signature 等)
  • 不能使用 connectionhostkeep-alive 等受限 header

响应语义

Webhook 采用 fire-and-forget 模式:

  • 响应体被忽略,不影响消息发送流程
  • 2xx 状态码视为成功,清除 last_error
  • 非 2xx / 超时 / 网络错误 → 写入 last_error 状态(可在 Agent 管理页面查看)

签名验证(Webhook Secret)

配置 Webhook Secret 后,系统会使用 HMAC-SHA256 对请求体进行签名,签名值放在 X-Webhook-Signature header 中。

签名算法

signature = HMAC-SHA256(webhook_secret, raw_request_body).hex()

关键:签名基于原始请求体(raw bytes),不是解析后重新序列化的 JSON。

Node.js 验证示例

javascript
import crypto from 'crypto'
import { timingSafeEqual } from 'crypto'

// Express 中间件 — 需要 raw body
// app.use('/webhook', express.raw({ type: 'application/json' }))

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody) // 必须是原始 bytes,不是 JSON.parse 后再 stringify
    .digest('hex')

  // 使用 timing-safe 比较,防止时序攻击
  const sig = Buffer.from(signature, 'utf8')
  const exp = Buffer.from(expected, 'utf8')
  if (sig.length !== exp.length) return false
  return timingSafeEqual(sig, exp)
}

// Express 路由
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature']
  if (!signature) {
    return res.status(401).send('Missing signature')
  }

  if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(403).send('Invalid signature')
  }

  const payload = JSON.parse(req.body.toString())
  console.log('Received webhook:', payload.event, payload.message.id)

  // 幂等处理:检查 message.id 是否已处理过
  // ...

  res.status(200).send('OK')
})

Python 验证示例

python
import hmac
import hashlib
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret'

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    if not signature:
        return 'Missing signature', 401

    # 使用原始请求体计算签名
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.get_data(),  # 原始 bytes,不是 request.json
        hashlib.sha256
    ).hexdigest()

    # 使用 timing-safe 比较
    if not hmac.compare_digest(signature, expected):
        return 'Invalid signature', 403

    payload = request.get_json()
    print(f"Received webhook: {payload['event']} {payload['message']['id']}")

    # 幂等处理:检查 message.id 是否已处理过
    # ...

    return 'OK', 200

自定义 Headers vs Webhook Secret

自定义 HeadersWebhook Secret
用途身份认证(证明"我有权访问")完整性验证(证明"请求未被篡改且来自外脑")
内容固定 key-value(如 Authorization: Bearer xxxHMAC 签名(动态,每次请求不同)
典型场景调用需要 API Key 的第三方接口验证请求来源可信、防中间人篡改
是否必须可选可选

建议: 如果你的接收端是公网可访问的,建议同时使用两者 — Custom Headers 用于鉴权,Webhook Secret 用于验证请求完整性。


安全特性

特性说明
HTTPS 强制Webhook URL 必须以 https:// 开头
SSRF 防护双重校验:字符串层(禁止私有 IP 格式)+ DNS 解析层(实际解析后再校验)
DNS 固定域名解析后固定到已验证的 IP 发送请求,消除 TOCTOU(检查时和使用时不一致)窗口
请求超时10 秒超时,防止慢响应拖垮系统
禁止重定向redirect: 'error',防止通过重定向绕过 SSRF 防护
Header 注入防护RFC 7230 格式校验,禁止换行符和受限 header

AI Workflow Editor