# 事件投递

通过 Webhook 让点金平台在关键事件发生时主动回调你的服务，实现外部审计、风险拦截或系统联动。

## 什么是 Webhook

Webhook 是点金平台**主动调用**外部 HTTP 接口的回调机制。当某个关键事件（如收到用户消息、工具调用前后、智能体执行完毕等）在平台内发生时，平台会按你配置的地址发起 POST 请求，把事件上下文以 JSON 投递给你的服务。

| 维度 | 说明 |
| --- | --- |
| 触发方 | 点金平台主动调用（不是你定时拉取） |
| 协议 | HTTP(S) POST + JSON Body（生产建议 HTTPS） |
| 鉴权 | 可选：`X-Webhook-Signature` HMAC 签名 + `X-Webhook-Timestamp` 时间戳，用于防伪造与防重放 |
| 失败策略 | 拦截型可配 `ALLOW`（失败放行主流程）或 `DENY`（失败终止主流程） |
| 配置粒度 | 两个配置面：**应用空间 Webhook**（挂载到指定 Agent 生效，首推）+ **租户设置「事件管理」**（启用后对租户内全部 Agent 生效）；命中同一事件时合并后按 `hookOrder` 升序执行 |

> **典型用途：** 敏感词审核 · 危险工具调用拦截 · 调用链路审计 · Token 消耗实时上报 · 与外部 IM / 工单系统联动。

## 事件类型

> 按"是否挂起主流程"的标准，划分为两类，共 8 种事件：

**拦截型（3 个，可改写或拒绝执行，会阻塞主流程等待回调返回）**

| 事件 | 触发时机 |
| --- | --- |
| `user_message` | 收到用户消息时 — 可审核敏感词、改写输入或直接拒绝 |
| `pre_tool_use` | 工具调用前 — 可拦截危险调用或修改参数 |
| `pre_scheduled_task` | 定时任务执行前 — 可拦截或调整执行参数 |

**观察型（5 个，仅通知不阻断，平台异步投递不等待）**

| 事件 | 触发时机 |
| --- | --- |
| `post_tool_use` | 工具调用完成后 — 用于审计、耗时监控 |
| `session_start` | 会话创建或恢复时 |
| `agent_completion` | 智能体整轮执行结束 — 含成功 / 失败信息 |
| `post_llm_call` | LLM 每次推理完成 — 记录 Token 消耗 |
| `post_scheduled_task` | 定时任务执行完毕 — 用于结果上报 |

> **如何选择**
> 
> **优先选观察型。** 拦截型 Hook 处于主流程关键路径，回调超时或返回 5xx 会直接影响用户体验；只在确实需要"修改 / 拒绝"输入时才使用。

## 在哪里配置

控制台提供**两个配置面**：在「应用空间」中配置的 Webhook 通过**挂载到指定智能体**生效；在「租户设置」中配置的 Hook **启用后对租户内全部 Agent 生效**。两个配置面分别兼顾灵活性（按 Agent 精准挂载）与便捷性（全局一键生效）。

| 配置面 | 入口 | 说明 |
| --- | --- | --- |
| 应用空间 Webhook | 应用空间侧边栏 → 「Webhook」 | 在当前应用下创建 Webhook；创建后需在智能体详情中挂载生效 |
| 挂载到智能体 | 应用空间 → 智能体 → Agent 详情 → 「Webhook（已挂载 N）」卡片 → 「管理」 | 勾选需要在该智能体运行时生效的 Webhook，保存时以选中项**全量覆盖** |
| 租户设置事件管理 | 租户设置 → 「事件管理」 | 创建并启用 Hook，启用后对租户内全部 Agent 生效；可设执行顺序（`hookOrder`，数值越小越先执行） |

两处的编辑页均提供「DryRun 调试」面板（不入库直接试投递）。若只想对个别 Agent 生效，请使用下方的 [Agent 级挂载](#agent-level)。

## Agent 级挂载

应用空间 Webhook 背后是一套「**无状态 Webhook 注册表 + Agent 挂载**」的两层解耦模型：先在注册表登记 Webhook 配置，再把配置精准挂载到需要的 Agent 上，配置与生效范围互相独立。

| 层 | 作用 | 控制台入口 |
| --- | --- | --- |
| Webhook 注册表 | 管理 Webhook 配置本体（名称 / 事件 / 回调地址 / 签名等）；仅登记不生效 | 应用空间侧边栏 → 「Webhook」（页面「Webhook 管理」） |
| Agent 挂载 | 把注册表中的配置挂载到指定 Agent，挂载后才参与该 Agent 的事件投递 | Agent 详情 → 「Webhook（已挂载 N）」卡片 → 「管理」（保存时以选中项**全量覆盖**） |

应用级 Webhook 没有独立的启停开关：**挂载即生效、取消挂载即失效**。

**运行时合并规则：**

- 租户级 Hook 与 Agent 挂载的 Webhook 会合并为一个执行列表，按 `hookOrder` 统一升序执行。
- Agent 挂载独立生效：即使租户未配置任何租户级 Hook，挂载到 Agent 的 Webhook 同样会被触发。
- 拦截型事件中任一 Hook 返回拒绝即短路，后续 Hook 不再执行。

> **推荐用法：** 首推在应用空间配置 Webhook 并挂载到目标 Agent；确需全局生效的事件（如审计）可在租户设置的事件管理中配置；**不推荐在租户级配置拦截型 Hook**，失败或拒绝会阻断租户内所有 Agent 的执行。

## 如何配置

以下以**租户设置 → 事件管理**为例；应用空间 Webhook 的创建流程与之类似，创建后需在[智能体详情中挂载](#agent-level)生效。

> **接入流程：** 进入事件管理 → 创建 Hook → 选执行时机 → 填回调地址 → DryRun 调试 → 启用

### Step 1: 进入事件管理

登录租户控制台后，进入「租户设置 → 事件管理」，即可看到当前租户下的全部 Hook。

[打开事件管理 →](https://dianjin.biz.aliyun.com/tenant-admin/settings/hooks)

### Step 2: 点击「创建 Hook」

在列表页右上角点击「创建 Hook」按钮，弹出创建表单。

如果是首次接入，建议先创建一个**观察型** Hook（如 `agent_completion`）跑通链路，再逐步替换为拦截型。

### Step 3: 填写表单四要素

名称 / 执行时机 / 回调地址 / 失败策略

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| 名称 | 是 | 业务可读的名称，如「用户消息审核」 |
| 执行时机 | 是 | 8 种事件中多选 N 种；同一 Hook 可同时订阅多个事件 |
| 回调地址 | 是 | 你的服务接收事件的 URL（`http://` 或 `https://` 开头，生产建议 HTTPS），平台以 POST + JSON 投递 |
| 失败策略 | 拦截型必填 | `ALLOW`（回调失败时放行主流程）或 `DENY`（直接终止） |
| 签名密钥 | 否 | 推荐填写；平台会用此密钥对请求体计算 HMAC，放在 `X-Webhook-Signature` 头中 |
| 超时 | 否 | 拦截型默认 3000ms，观察型默认 5000ms；超过即按失败策略处理 |

**高级配置（可选字段）**

| 字段 | 说明 |
| --- | --- |
| `toolNameMatch` | 工具名过滤，仅对 `pre_tool_use` / `post_tool_use` 生效，默认 `*`。四种写法：`*` 或留空 = 全部工具；`/正则/`（斜杠包裹）= 正则匹配；`a|b` = 管道分隔枚举，命中其一即匹配；其余 = 与工具名精确匹配。**不支持 glob 通配** |
| `auth` | 回调鉴权，复用统一鉴权模型 `FinxHttpAuth`，支持 `NONE` / `API_KEY` / `BEARER` / `BASIC` / `CUSTOM` / `CUSTOM_HTTP_TOKEN` 六种类型 |
| `maxRetries` | 仅观察型生效，默认 3；只截断快档重试次数（0~3 档），不影响分钟级阶梯，详见[响应与可靠性](#response-and-reliability) |
| `signingSecret` 更新 | 编辑时**不传或传 `***`** = 保留原密钥；**传新值** = 替换；**传空字符串** = 清除密钥（后续投递不再签名） |

### Step 4: DryRun 调试 + 启用

保存后，先用控制台的「DryRun 调试」面板试投递一次再上线。

DryRun 会按你勾选的事件类型逐一构造 mock 事件，用与生产**完全一致**的信封、签名与鉴权真实调用你的回调地址（同步执行、不重试、不落库、不影响主流程），并返回完整的请求 / 响应快照与分段耗时（`durationMs` / `connectMs` / `readBodyMs`），便于定位慢在建连还是读响应。调试失败时还可使用基于 LLM 的**智能诊断**分析失败原因，或用自然语言**生成配置草稿**。看到 200 OK 后即可在列表页打开开关启用 Hook，**变更立即生效**，无需重启。

> **注意事项**
> 
> - 拦截型 Hook 的 `failureMode=DENY` 会**阻断主流程**，请确认你的服务足够稳定（建议 ≥ 99.9% 可用性）后再启用。
> - 签名密钥仅在创建 / 编辑时由你自行填入，平台不会回显明文；丢失后只能在编辑页重置。
> - 同租户下多个 Hook 命中同一事件时，按 `hookOrder` 由小到大顺序执行；任一拦截型返回拒绝即提前终止。

## 响应与可靠性

**你的回调服务需要返回什么：**

| 类型 | 响应约定 |
| --- | --- |
| 观察型 | 返回 HTTP 2xx 即视为投递成功，响应体被忽略 |
| 拦截型 | 返回 HTTP 2xx + JSON `decision` 决策（`allow` / `deny` / `modify`，不区分大小写）；空 body 或缺 `decision` 按放行处理 |

**投递可靠性要点：**

- **拦截型不重试**：超时 / 非 2xx / 解析失败直接按 `failureMode` 降级（`ALLOW` 放行或 `DENY` 拒绝）。
- **观察型阶梯重试**：首投失败后先按秒级快档快速重试（档数受 `maxRetries` 截断），仍失败则进入分钟级阶梯持续重投，默认配置下总投递窗口约 70 分钟；仅 `429` / `5xx` / 网络类错误触发重试，其余 `4xx` 不重试。
- **按 `eventId` 幂等**：重试期间 `eventId` 与 `occurredAt` 不变，`dispatchedAt` / `deliveryAttempt` 每次更新并重新签名；同一事件可能被多次接收，你的服务必须做幂等去重。
- **熔断保护**：同一回调地址连续出现超时 / 网络类故障会被短暂熔断，期间拦截型直接按 `failureMode` 短路降级（平台内部保护参数，可能调整）。

信封字段、8 种事件的 context 结构、签名验签与重试阶梯明细，见 [Webhook 事件结构](/docs/quickstart/data-model.md#data-webhook)。

## 最佳实践与常见问题

**最佳实践：**

- **优先观察型**：拦截型处于主流程关键路径，只有确实需要「修改 / 拒绝」输入时才使用。
- **快速 ACK**：拦截型建议 1 秒内返回；耗时业务（二次 RPC、落库）放到观察型里异步处理。
- **务必验签**：生产环境配置 `signingSecret` 并校验签名 + 时间戳窗口，防伪造与重放。
- **按 `eventId` 幂等**：观察型重试可能导致同一事件被接收多次，去重后再消费。
- **拦截型不要租户级**：审批 / 拦截型 Hook 首推在应用空间配置并通过 [Agent 级挂载](#agent-level)精准生效；不推荐在租户级配置拦截型 Hook，失败或拒绝会阻断租户内所有 Agent 的执行。
- **上线前先 DryRun**：用 DryRun 面板验证连通性、签名与响应格式，再打开启用开关。

**Q: 为什么我的服务没有收到投递？**

按顺序排查：① Hook 是否已启用、事件类型是否勾选正确；② 回调地址是否公网可达（以 `http://` 或 `https://` 开头）；③ `pre_tool_use` / `post_tool_use` 检查 `toolNameMatch` 是否把目标工具过滤掉了；④ 你的服务是否返回了非 2xx（`4xx` 不会重试）；⑤ 地址连续超时可能触发熔断，稍后会自动探测恢复。用 DryRun 调试可直接看到完整请求 / 响应快照与分段耗时。

**Q: 拦截型 Hook 应该返回什么格式？**

HTTP 2xx + JSON：`{"decision": "allow"}` 放行；`{"decision": "deny", "reason": "..."}` 拒绝并反馈原因；`{"decision": "modify", "modifiedValue": "..."}` 修改后放行。`decision` 不区分大小写；空响应体或缺 `decision` 按放行处理。完整容错规则见[拦截型响应格式](/docs/quickstart/data-model.md#webhook-response)。

**Q: 如何验证签名、防止重放攻击？**

配置 `signingSecret` 后，平台对「时间戳 + `.` + 原始请求体」计算 HMAC-SHA256，放在 `X-Webhook-Signature` 头（`sha256=<hex>` 格式）。你的服务应先校验 `X-Webhook-Timestamp` 与本地时钟偏差（建议 ±5 分钟窗口），再用同一密钥重算签名并做常时间比较。详见[签名算法](/docs/quickstart/data-model.md#webhook-signature)。

**Q: 如何让 Hook 只对某个 Agent 生效？**

不要用租户级配置，改用 [Agent 级挂载](#agent-level)。在应用空间「Webhook」页创建配置，再进入「智能体 → Agent 详情 → 『Webhook（已挂载 N）』卡片 → 管理」勾选并保存。注意保存是**全量覆盖**语义，以本次勾选结果为准，取消全部勾选即清空该 Agent 的挂载。

**Q: 多个 Hook 命中同一事件时按什么顺序执行？**

租户级 Hook 与 Agent 挂载的 Webhook 合并后，按 `hookOrder`（取值 0~9999，默认 100）升序统一执行；拦截型事件中任一 Hook 返回拒绝即短路，后续 Hook 不再执行。
