> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> 通过 HTTP Webhook 订阅 Cloud Agents 的生命周期事件:端点 API、投递与签名校验,以及完整的事件目录。

## 概述

Webhook 是 Qoder Cloud Agents 提供的事件驱动推送机制。当 Agent、Session 等资源发生生命周期变化时，系统以 HTTP POST 方式将结构化事件推送到开发者注册的 URL，无需轮询即可实时获取状态变更。

**核心特性：**

* **事件驱动推送** — 资源状态变更时主动通知，无需客户端轮询
* **信封结构** — 统一的 `BetaWebhookEvent { id, created_at, data, type:"event" }` 格式
* **投递语义** — at-least-once 保证；HMAC-SHA256 签名验证；指数退避重试
* **通配符订阅** — 使用 `*` 订阅所有事件类型，简化集成配置

**适用场景：**

| 场景           | 说明                  |
| ------------ | ------------------- |
| 异步任务完成通知     | Session 执行完毕后触发下游流程 |
| Agent 配置变更审计 | 记录 Agent 创建/更新/删除操作 |
| 多 Agent 协作编排 | Thread 状态变更驱动子任务调度  |
| 运维监控告警       | 连续失败计数超阈值时告警        |

***

## Domain Types

本节定义 Webhook 事件的数据结构。每种事件类型的 `data` 字段遵循统一的 object 格式，包含资源 ID、事件类型及事件特定的额外字段。

***

### Session 生命周期事件

***

### Webhook Session Created Event Data

* `WebhookSessionCreatedEventData object { id, type }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.created"`

    * `"session.created"`

    Session 被成功创建时触发。通过 `POST /sessions` 接口创建 Session 后，系统立即发送此事件。

***

### Webhook Session Updated Event Data

* `WebhookSessionUpdatedEventData object { id, type }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.updated"`

    * `"session.updated"`

    Session 元数据（如 title、metadata 等）被修改时触发。

***

### Webhook Session Archived Event Data

* `WebhookSessionArchivedEventData object { id, type }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.archived"`

    * `"session.archived"`

    Session 被归档时触发。归档后 Session 不再接受新消息，但历史数据仍可查询。

***

### Webhook Session Deleted Event Data

* `WebhookSessionDeletedEventData object { id, type }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.deleted"`

    * `"session.deleted"`

    Session 被永久删除时触发。删除后该 Session 的所有数据将不可恢复。

***

### Session 状态事件

***

### Webhook Session Status Run Started Event Data

* `WebhookSessionStatusRunStartedEventData object { id, type }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.status_run_started"`

    * `"session.status_run_started"`

    Agent 开始一轮运行时触发。标志着 Session 进入 running 状态，正在执行用户指令。

***

### Webhook Session Status Idled Event Data

* `WebhookSessionStatusIdledEventData object { id, type }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.status_idled"`

    * `"session.status_idled"`

    一轮 turn 执行完成，Session 回到 idle 状态时触发。此时可安全地读取 Session 的最新输出。

***

### Session Thread 事件

适用于多 Agent 协作场景。Thread 事件在 Session 事件基础上额外携带 `session_thread_id` 字段，标识具体的执行线程。

***

### Webhook Session Thread Created Event Data

* `WebhookSessionThreadCreatedEventData object { id, type, session_thread_id }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.thread_created"`

    * `"session.thread_created"`

    新的 Session Thread 被创建时触发。常见于子 Agent 协作场景中，主 Agent 派生子线程执行任务。

  * `session_thread_id: string`

    所属的 Session Thread ID。

***

### Webhook Session Thread Idled Event Data

* `WebhookSessionThreadIdledEventData object { id, type, session_thread_id }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.thread_idled"`

    * `"session.thread_idled"`

    Session Thread 一轮执行结束进入 idle 状态时触发。表示该线程已完成当前任务，可读取结果。

  * `session_thread_id: string`

    所属的 Session Thread ID。

***

### Webhook Session Thread Terminated Event Data

* `WebhookSessionThreadTerminatedEventData object { id, type, session_thread_id }`

  * `id: string`

    触发事件的 Session ID。

  * `type: "session.thread_terminated"`

    * `"session.thread_terminated"`

    Session Thread 被终止时触发。Thread 终止后不可恢复，需创建新 Thread 继续工作。

  * `session_thread_id: string`

    所属的 Session Thread ID。

***

### Agent 生命周期事件

Agent 事件在基础字段之外额外携带 `version` 字段，标识 Agent 的配置版本号。

***

### Webhook Agent Created Event Data

* `WebhookAgentCreatedEventData object { id, type, version }`

  * `id: string`

    触发事件的 Agent ID。

  * `type: "agent.created"`

    * `"agent.created"`

    Agent 被成功创建时触发。通过 `POST /agents` 接口创建 Agent 后，系统发送此事件。

  * `version: integer`

    Agent 版本号。首次创建时为 `1`。

***

### Webhook Agent Updated Event Data

* `WebhookAgentUpdatedEventData object { id, type, version }`

  * `id: string`

    触发事件的 Agent ID。

  * `type: "agent.updated"`

    * `"agent.updated"`

    Agent 配置被更新时触发。每次更新 `version` 递增。

  * `version: integer`

    Agent 版本号。

***

### Webhook Agent Archived Event Data

* `WebhookAgentArchivedEventData object { id, type, version }`

  * `id: string`

    触发事件的 Agent ID。

  * `type: "agent.archived"`

    * `"agent.archived"`

    Agent 被归档时触发。归档后 Agent 不再接受新的 Session 创建请求。

  * `version: integer`

    Agent 版本号。

***

### Webhook Agent Deleted Event Data

* `WebhookAgentDeletedEventData object { id, type, version }`

  * `id: string`

    触发事件的 Agent ID。

  * `type: "agent.deleted"`

    * `"agent.deleted"`

    Agent 被删除时触发。删除后该 Agent 的所有配置数据将不可恢复。

  * `version: integer`

    Agent 版本号。

***

## Webhook Endpoint API

管理 Webhook 端点的 CRUD 接口。通过这些接口可以创建、查询、更新、删除 Webhook 端点，以及发送测试事件和控制端点的启用/禁用状态。

**Base URL：**

| 区域     | 地址                                      |
| ------ | --------------------------------------- |
| Global | `https://api.qoder.com/api/v1/cloud`    |
| CN     | `https://api.qoder.com.cn/api/v1/cloud` |

**认证方式：**

所有接口均需要在请求头中携带 Personal Access Token：

```
Authorization: Bearer $PAT
```

***

### POST /webhook\_endpoints

创建一个新的 Webhook 端点。

**请求参数：**

| 字段            | 类型        | 必填 | 说明                                         |
| ------------- | --------- | -- | ------------------------------------------ |
| `url`         | string    | 是  | 接收事件推送的 HTTP 或 HTTPS URL（生产环境强烈建议使用 HTTPS） |
| `description` | string    | 否  | 端点描述，便于管理识别                                |
| `events`      | string\[] | 是  | 订阅的事件类型列表，支持 `*` 通配符                       |

**响应：** `201 Created`

```json theme={null}
{
  "id": "5fc05310-4d9c-447e-a4b8-f124f17e1ff0",
  "url": "https://webhook.site/your-unique-path",
  "description": "quickstart demo",
  "events": ["*"],
  "active": true,
  "signing_secret": "whsec_BfJDodFEzmkdjAUf19-_XthSq6KbPKmRjfm9KFWMIuk",
  "signing_secret_version": 1,
  "created_at": "2026-07-02T18:17:30.069191+08:00"
}
```

> **注意：** `signing_secret` 仅在创建时返回一次，请妥善保存。后续查询接口不会再次返回此字段。

**状态码：**

| 状态码 | 说明                         |
| --- | -------------------------- |
| 201 | 创建成功                       |
| 400 | 请求参数无效（如 URL 格式错误、事件类型不合法） |
| 401 | 未授权，Token 无效或过期            |
| 422 | 语义错误（如 URL 重复注册）           |

**示例：**

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/webhook_endpoints \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://webhook.site/your-unique-path",
    "description": "quickstart demo",
    "events": ["*"]
  }'
```

***

### GET /webhook\_endpoints

获取当前账户下所有 Webhook 端点列表。

**请求参数：** 无

**响应：** `200 OK`

```json theme={null}
{
  "data": [
    {
      "id": "700e7789-edab-41cb-b60a-210d0df38ab6",
      "url": "https://myapp.example.com/hooks/qoder",
      "description": "prod app hook",
      "events": ["session.created", "session.thread_idled"],
      "active": true,
      "signing_secret_version": 1,
      "consecutive_fail": 0,
      "last_success_at": "2026-07-02T18:08:12.666783+08:00",
      "created_at": "2026-07-02T17:47:16.073822+08:00",
      "updated_at": "2026-07-02T17:47:16.073822+08:00"
    }
  ]
}
```

**响应字段说明：**

| 字段                       | 类型        | 说明                                |
| ------------------------ | --------- | --------------------------------- |
| `id`                     | string    | 端点唯一标识                            |
| `url`                    | string    | 接收事件的 URL                         |
| `description`            | string    | 端点描述                              |
| `events`                 | string\[] | 订阅的事件类型列表                         |
| `active`                 | boolean   | 是否启用                              |
| `signing_secret_version` | integer   | 签名密钥版本号                           |
| `consecutive_fail`       | integer   | 连续投递失败次数                          |
| `last_success_at`        | string    | 最近一次成功投递时间（RFC 3339）              |
| `last_failure_at`        | string    | 最近一次投递失败时间（RFC 3339），无失败记录时为 null |
| `created_at`             | string    | 创建时间（RFC 3339）                    |
| `updated_at`             | string    | 最近更新时间（RFC 3339）                  |

**示例：**

```bash theme={null}
curl https://api.qoder.com/api/v1/cloud/webhook_endpoints \
  -H "Authorization: Bearer $PAT"
```

***

### GET /webhook\_endpoints/\{id}

获取指定 Webhook 端点的详细信息。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 端点 ID |

**响应：** `200 OK`

返回单个端点对象，结构与列表接口中的元素一致。

**状态码：**

| 状态码 | 说明    |
| --- | ----- |
| 200 | 成功    |
| 404 | 端点不存在 |

**示例：**

```bash theme={null}
curl https://api.qoder.com/api/v1/cloud/webhook_endpoints/700e7789-edab-41cb-b60a-210d0df38ab6 \
  -H "Authorization: Bearer $PAT"
```

***

### PUT /webhook\_endpoints/\{id}

更新指定 Webhook 端点的配置。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 端点 ID |

**请求参数：**

| 字段            | 类型        | 必填 | 说明            |
| ------------- | --------- | -- | ------------- |
| `url`         | string    | 否  | 更新接收事件推送的 URL |
| `description` | string    | 否  | 更新端点描述        |
| `events`      | string\[] | 否  | 更新订阅的事件类型列表   |

**响应：** `200 OK`

返回更新后的完整端点对象。

**状态码：**

| 状态码 | 说明     |
| --- | ------ |
| 200 | 更新成功   |
| 400 | 请求参数无效 |
| 404 | 端点不存在  |

**示例：**

```bash theme={null}
curl -X PUT https://api.qoder.com/api/v1/cloud/webhook_endpoints/700e7789-edab-41cb-b60a-210d0df38ab6 \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["session.created", "session.status_idled", "agent.updated"],
    "description": "updated prod hook"
  }'
```

***

### DELETE /webhook\_endpoints/\{id}

永久删除指定的 Webhook 端点。删除后所有未投递的事件将被丢弃。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 端点 ID |

**响应：** `204 No Content`

**状态码：**

| 状态码 | 说明    |
| --- | ----- |
| 204 | 删除成功  |
| 404 | 端点不存在 |

**示例：**

```bash theme={null}
curl -X DELETE https://api.qoder.com/api/v1/cloud/webhook_endpoints/700e7789-edab-41cb-b60a-210d0df38ab6 \
  -H "Authorization: Bearer $PAT"
```

***

### POST /webhook\_endpoints/\{id}/test

向指定端点发送一个测试事件，用于验证端点连通性和签名验证逻辑。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 端点 ID |

**请求参数：** 无

**响应：** `202 Accepted`

```json theme={null}
{"event_id": 433, "delivery_rows": 2}
```

**响应字段说明：**

| 字段              | 类型      | 说明         |
| --------------- | ------- | ---------- |
| `event_id`      | integer | 测试事件的内部 ID |
| `delivery_rows` | integer | 事件发布的消息分区数 |

**状态码：**

| 状态码 | 说明      |
| --- | ------- |
| 202 | 测试事件已发送 |
| 404 | 端点不存在   |

**示例：**

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/webhook_endpoints/700e7789-edab-41cb-b60a-210d0df38ab6/test \
  -H "Authorization: Bearer $PAT"
```

***

### POST /webhook\_endpoints/\{id}/enable

启用一个被禁用的 Webhook 端点。启用后端点将重新接收事件推送。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 端点 ID |

**请求参数：** 无

**响应：** `200 OK`

返回启用后的端点对象（`active: true`）。

**状态码：**

| 状态码 | 说明    |
| --- | ----- |
| 200 | 启用成功  |
| 404 | 端点不存在 |

**示例：**

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/webhook_endpoints/700e7789-edab-41cb-b60a-210d0df38ab6/enable \
  -H "Authorization: Bearer $PAT"
```

***

### POST /webhook\_endpoints/\{id}/disable

禁用一个 Webhook 端点。禁用后端点将停止接收事件推送，但不会被删除。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 端点 ID |

**请求参数：** 无

**响应：** `200 OK`

返回禁用后的端点对象（`active: false`）。

**状态码：**

| 状态码 | 说明    |
| --- | ----- |
| 200 | 禁用成功  |
| 404 | 端点不存在 |

**示例：**

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/webhook_endpoints/700e7789-edab-41cb-b60a-210d0df38ab6/disable \
  -H "Authorization: Bearer $PAT"
```

***

### GET /webhook\_events

列出 Webhook 事件投递记录，用于审计和排查。

**请求参数：** 无

**响应：** `200 OK`

返回事件列表，包含事件的投递状态、时间戳等信息。

**示例：**

```bash theme={null}
curl https://api.qoder.com/api/v1/cloud/webhook_events \
  -H "Authorization: Bearer $PAT"
```

***

### GET /webhook\_events/\{id}

获取单个 Webhook 事件的详细信息。

**路径参数：**

| 参数   | 类型     | 说明            |
| ---- | ------ | ------------- |
| `id` | string | Webhook 事件 ID |

**响应：** `200 OK`

**状态码：**

| 状态码 | 说明    |
| --- | ----- |
| 200 | 成功    |
| 404 | 事件不存在 |

**示例：**

```bash theme={null}
curl https://api.qoder.com/api/v1/cloud/webhook_events/433 \
  -H "Authorization: Bearer $PAT"
```

***

### 错误响应格式

所有接口在遇到错误时，返回统一的错误结构：

```json theme={null}
{
  "error": {
    "message": "Unknown event type: agent.updated",
    "type": "invalid_request_error"
  },
  "request_id": "cc865161-b25d-4d68-bad5-bf0363f6e0f9",
  "type": "error"
}
```

**错误字段说明：**

| 字段              | 类型     | 说明                                                |
| --------------- | ------ | ------------------------------------------------- |
| `error.message` | string | 人类可读的错误描述                                         |
| `error.type`    | string | 错误分类（如 `invalid_request_error`、`not_found_error`） |
| `request_id`    | string | 请求追踪 ID，用于排查问题时提供给支持团队                            |
| `type`          | string | 固定值 `"error"`                                     |

> **注意：** 鉴权层（如 Token 无效或缺失）返回的 401 响应可能不遵循上述业务错误结构，而是由网关直接返回。

***

## Webhook Delivery（投递机制）

### 投递方式

系统通过 HTTP POST 将事件推送到注册的 URL，请求格式如下：

* **Method:** `POST`
* **Content-Type:** `application/json`
* **Body:** JSON 格式的信封结构（详见[信封结构详解](#信封结构详解)）

### 请求头

每次投递包含以下 HTTP 请求头：

| Header              | 说明                                                       |
| ------------------- | -------------------------------------------------------- |
| `Content-Type`      | `application/json`                                       |
| `User-Agent`        | `QoderCloudAgents-Webhook/1.0`                           |
| `Webhook-Signature` | HMAC-SHA256 签名，格式为 `t=<unix_epoch>,v1=<hmac_sha256_hex>` |

### 签名验证

为确保事件来源的真实性和数据完整性，每次投递都携带 HMAC-SHA256 签名。开发者应在接收端验证签名后再处理事件。

**签名格式：**

```
t=1719907336,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8f9
```

**签名算法：**

```
signature = HMAC_SHA256(signing_secret, "<t>.<raw_body>")
```

其中：

* `signing_secret` — 创建端点时返回的密钥（`whsec_` 前缀）
* `t` — 签名时的 Unix 时间戳（秒）
* `raw_body` — 请求体的原始字节内容（未经解析）

**验证步骤：**

1. 从 `Webhook-Signature` Header 中提取 `t` 和 `v1`
2. 检查时间戳是否在容忍窗口内（建议 600 秒），防止重放攻击
3. 使用 `signing_secret` 对 `"<t>.<raw_body>"` 计算 HMAC-SHA256
4. 将计算结果与 `v1` 进行恒时比较（timing-safe comparison）

### 签名验证代码示例

**Node.js：**

```js theme={null}
const crypto = require('node:crypto');

function verifyWebhook(secret, rawBody, header, toleranceSec = 600) {
  const parts = Object.fromEntries(
    header.split(',').map(kv => kv.split('=').map(s => s.trim()))
  );
  const ts = parseInt(parts.t, 10);
  if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(parts.v1, 'hex')
  );
}
```

**Python：**

```python theme={null}
import hmac, hashlib, time

def verify_webhook(secret: bytes, raw_body: bytes, header: str, tolerance_sec: int = 600) -> bool:
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    ts = int(parts["t"])
    if abs(time.time() - ts) > tolerance_sec:
        return False
    signed_payload = f"{ts}.".encode() + raw_body
    expected = hmac.new(secret, signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

**Go：**

```go theme={null}
func verifyWebhook(secret, rawBody []byte, header string, toleranceSec int64) bool {
    var ts int64; var v1 string
    for _, kv := range strings.Split(header, ",") {
        kv = strings.TrimSpace(kv)
        if strings.HasPrefix(kv, "t=") { ts, _ = strconv.ParseInt(kv[2:], 10, 64) }
        if strings.HasPrefix(kv, "v1=") { v1 = kv[3:] }
    }
    if ts == 0 || v1 == "" { return false }
    if diff := time.Now().Unix() - ts; diff > toleranceSec || diff < -toleranceSec { return false }
    mac := hmac.New(sha256.New, secret)
    fmt.Fprintf(mac, "%d.", ts)
    mac.Write(rawBody)
    return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(v1))
}
```

### 重试策略

当投递失败时，系统采用指数退避策略进行重试：

| 尝试次数  | 延迟   | 说明    |
| ----- | ---- | ----- |
| 第 1 次 | 立即   | 首次投递  |
| 第 2 次 | 1 秒  | 第一次重试 |
| 第 3 次 | 5 秒  | 第二次重试 |
| 第 4 次 | 30 秒 | 最终重试  |

共计 4 次尝试（1 次投递 + 3 次重试）。全部失败后事件进入死信队列。

### 响应码处理

| 响应码范围 | 处理方式                       |
| ----- | -------------------------- |
| 2xx   | 投递成功，事件标记为已送达              |
| 4xx   | 不重试，事件标记为已丢弃（客户端错误应由开发者修复） |
| 5xx   | 触发重试（服务端临时故障）              |
| 超时    | 触发重试（默认超时 30 秒）            |

### 自动降级

当某个端点的 `consecutive_fail` 计数超过 **20** 时，系统将触发降级警告。建议开发者监控此指标，并在持续失败时检查：

* 端点 URL 是否可达
* SSL 证书是否有效
* 服务端是否正常响应
* 签名验证逻辑是否存在 Bug

***

## Supported Event Types（完整清单）

### 事件类型总览

| 事件类型                                      | 状态        | data 额外字段           | 触发条件                        |
| ----------------------------------------- | --------- | ------------------- | --------------------------- |
| `session.created`                         | ✅ 已实现     | —                   | POST /sessions 成功创建 Session |
| `session.updated`                         | ✅ 已实现     | —                   | Session 元数据被修改              |
| `session.archived`                        | ✅ 已实现     | —                   | Session 被归档                 |
| `session.deleted`                         | ✅ 已实现     | —                   | Session 被永久删除               |
| `session.status_run_started`              | ✅ 已实现     | —                   | Agent 开始一轮运行                |
| `session.status_idled`                    | ✅ 已实现     | —                   | Turn 执行完成，回到 idle 状态        |
| `session.thread_created`                  | ✅ 已实现     | `session_thread_id` | 新的 Session Thread 被创建       |
| `session.thread_idled`                    | ✅ 已实现     | `session_thread_id` | Thread 执行结束进入 idle          |
| `session.thread_terminated`               | ✅ 已实现     | `session_thread_id` | Thread 被终止                  |
| `agent.created`                           | ✅ 已实现     | `version`           | POST /agents 创建 Agent       |
| `agent.updated`                           | ✅ 已实现     | `version`           | Agent 配置被更新                 |
| `agent.archived`                          | ✅ 已实现     | `version`           | Agent 被归档                   |
| `agent.deleted`                           | ✅ 已实现     | `version`           | Agent 被删除                   |
| `session.pending`                         | 🔜 白名单已注册 | —                   | Session 等待调度                |
| `session.running`                         | 🔜 白名单已注册 | —                   | Session 正在运行                |
| `session.idled`                           | 🔜 白名单已注册 | —                   | Session 进入 idle             |
| `session.requires_action`                 | 🔜 白名单已注册 | —                   | Session 需要人工介入              |
| `session.status_run_failed`               | 🔜 白名单已注册 | —                   | 运行失败                        |
| `session.status_terminated`               | 🔜 白名单已注册 | —                   | Session 被终止                 |
| `session.status_rescheduled`              | 🔜 白名单已注册 | —                   | Session 被重新调度               |
| `session.status_paused_pending_input`     | 🔜 白名单已注册 | —                   | 暂停等待用户输入                    |
| `session.status_paused_pending_approval`  | 🔜 白名单已注册 | —                   | 暂停等待审批                      |
| `session.status_paused_user_intervention` | 🔜 白名单已注册 | —                   | 暂停等待人工干预                    |
| `session.outcome_evaluation_started`      | 🔜 白名单已注册 | —                   | 结果评估开始                      |
| `session.outcome_evaluation_ended`        | 🔜 白名单已注册 | —                   | 结果评估结束                      |
| `vault.created`                           | 🔜 白名单已注册 | —                   | 密钥库创建                       |
| `vault.deleted`                           | 🔜 白名单已注册 | —                   | 密钥库删除                       |
| `vault.updated`                           | 🔜 白名单已注册 | —                   | 密钥库更新                       |
| `vault_credential.created`                | 🔜 白名单已注册 | —                   | 凭据创建                        |
| `vault_credential.updated`                | 🔜 白名单已注册 | —                   | 凭据更新                        |
| `vault_credential.deleted`                | 🔜 白名单已注册 | —                   | 凭据删除                        |
| `vault_credential.shared`                 | 🔜 白名单已注册 | —                   | 凭据共享                        |
| `vault_credential.revoked`                | 🔜 白名单已注册 | —                   | 凭据撤销                        |
| `deployment.created`                      | 📋 规划中    | —                   | 部署创建                        |
| `deployment.updated`                      | 📋 规划中    | —                   | 部署更新                        |
| `deployment.archived`                     | 📋 规划中    | —                   | 部署归档                        |
| `deployment.deleted`                      | 📋 规划中    | —                   | 部署删除                        |
| `deployment.paused`                       | 📋 规划中    | —                   | 部署暂停                        |
| `deployment.unpaused`                     | 📋 规划中    | —                   | 部署恢复                        |
| `deployment_run.started`                  | 📋 规划中    | —                   | 部署运行开始                      |
| `deployment_run.failed`                   | 📋 规划中    | —                   | 部署运行失败                      |
| `deployment_run.succeeded`                | 📋 规划中    | —                   | 部署运行成功                      |
| `environment.created`                     | 📋 规划中    | —                   | 环境创建                        |
| `environment.updated`                     | 📋 规划中    | —                   | 环境更新                        |
| `environment.archived`                    | 📋 规划中    | —                   | 环境归档                        |
| `environment.deleted`                     | 📋 规划中    | —                   | 环境删除                        |
| `memory_store.created`                    | 📋 规划中    | —                   | 记忆存储创建                      |
| `memory_store.archived`                   | 📋 规划中    | —                   | 记忆存储归档                      |
| `memory_store.deleted`                    | 📋 规划中    | —                   | 记忆存储删除                      |

### 特殊事件类型

| 事件类型           | 说明                                               |
| -------------- | ------------------------------------------------ |
| `*`            | 通配符，订阅所有事件类型。在创建端点时使用 `["*"]` 可接收全部事件推送          |
| `webhook.test` | 内部测试事件，通过 `POST /webhook_endpoints/{id}/test` 触发 |

### 状态标记说明

| 标记        | 含义                                     |
| --------- | -------------------------------------- |
| ✅ 已实现     | 系统中存在完整的事件触发点，可正常订阅和接收                 |
| 🔜 白名单已注册 | 事件类型已在验证白名单中注册（可用于 `events` 字段），但暂无触发点 |
| 📋 规划中    | 事件类型已在 API 设计层面完成定义，尚未注册到白名单           |

***

## 信封结构详解

所有 Webhook 事件均使用统一的信封（envelope）结构进行封装投递。

### 基础信封结构

```json theme={null}
{
  "id": "whevt_a1b2c3d4e5f67890",
  "created_at": "2026-07-02T10:02:16Z",
  "type": "event",
  "data": {
    "id": "sess_019f224773fe71d79c5869bd089d159c",
    "type": "session.created"
  }
}
```

### Thread 事件信封

Thread 事件的 `data` 中额外包含 `session_thread_id` 字段：

```json theme={null}
{
  "id": "whevt_a1b2c3d4e5f67890",
  "created_at": "2026-07-02T10:02:17Z",
  "type": "event",
  "data": {
    "id": "sess_019f224773fe71d79c5869bd089d159c",
    "type": "session.thread_idled",
    "session_thread_id": "sthread_019f224..."
  }
}
```

### Agent 事件信封

Agent 事件的 `data` 中额外包含 `version` 字段：

```json theme={null}
{
  "id": "whevt_a1b2c3d4e5f67890",
  "created_at": "2026-07-02T10:02:16Z",
  "type": "event",
  "data": {
    "id": "agent_019f224773fe71d7...",
    "type": "agent.created",
    "version": 1
  }
}
```

### 字段说明

| 字段                       | 类型      | 说明                                                     |
| ------------------------ | ------- | ------------------------------------------------------ |
| `id`                     | string  | 事件唯一标识。格式为 `whevt_` + 唯一标识符，由事件的 idempotency key 确定性生成 |
| `created_at`             | string  | 事件创建时间，RFC 3339 UTC 格式                                 |
| `type`                   | string  | 固定值 `"event"`，标识这是一个事件信封                               |
| `data`                   | object  | 事件负载，包含触发资源的信息                                         |
| `data.id`                | string  | 触发事件的资源 ID（Session ID 或 Agent ID）                      |
| `data.type`              | string  | 事件类型字符串，如 `"session.created"`、`"agent.updated"`        |
| `data.session_thread_id` | string  | （仅 Thread 事件）所属的 Session Thread ID                     |
| `data.version`           | integer | （仅 Agent 事件）Agent 配置版本号                                |

### 幂等性处理

信封中的 `id` 字段可作为去重键（deduplication key）。由于投递语义为 at-least-once，同一事件可能被多次推送。接收端应：

1. 使用 `id` 作为唯一键进行去重
2. 在处理前检查该 `id` 是否已被消费
3. 确保事件处理逻辑的幂等性

***

## 规划中的事件（Coming Soon）

以下事件类型已在 API 设计层面完成对齐，将随各资源功能上线后逐步开放。具体时间线视产品需求确定。

### deployment.\* （部署生命周期，6 个事件）

| 事件类型                  | 触发条件            |
| --------------------- | --------------- |
| `deployment.created`  | 新部署被创建          |
| `deployment.updated`  | 部署配置被更新         |
| `deployment.archived` | 部署被归档           |
| `deployment.deleted`  | 部署被删除           |
| `deployment.paused`   | 部署被暂停（停止触发新运行）  |
| `deployment.unpaused` | 部署被恢复（重新开始触发运行） |

### deployment\_run.\* （部署运行状态，3 个事件）

| 事件类型                       | 触发条件       |
| -------------------------- | ---------- |
| `deployment_run.started`   | 一次部署运行开始执行 |
| `deployment_run.failed`    | 部署运行失败     |
| `deployment_run.succeeded` | 部署运行成功完成   |

### environment.\* （环境管理，4 个事件）

| 事件类型                   | 触发条件    |
| ---------------------- | ------- |
| `environment.created`  | 运行环境被创建 |
| `environment.updated`  | 环境配置被更新 |
| `environment.archived` | 环境被归档   |
| `environment.deleted`  | 环境被删除   |

### memory\_store.\* （记忆存储，3 个事件）

| 事件类型                    | 触发条件      |
| ----------------------- | --------- |
| `memory_store.created`  | 记忆存储实例被创建 |
| `memory_store.archived` | 记忆存储被归档   |
| `memory_store.deleted`  | 记忆存储被删除   |

### vault.\* （密钥库，3 个事件）

| 事件类型            | 触发条件     |
| --------------- | -------- |
| `vault.created` | 密钥库被创建   |
| `vault.updated` | 密钥库配置被更新 |
| `vault.deleted` | 密钥库被删除   |

### vault\_credential.\* （凭据管理，5 个事件）

| 事件类型                       | 触发条件           |
| -------------------------- | -------------- |
| `vault_credential.created` | 新凭据被添加到密钥库     |
| `vault_credential.updated` | 凭据信息被更新        |
| `vault_credential.deleted` | 凭据被删除          |
| `vault_credential.shared`  | 凭据被共享给其他 Agent |
| `vault_credential.revoked` | 凭据的共享权限被撤销     |

***

## 附录 A：快速接入指南

### 步骤 1：创建 Webhook 端点

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/webhook_endpoints \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/qoder",
    "description": "Production webhook",
    "events": ["session.status_idled", "session.thread_idled"]
  }'
```

### 步骤 2：保存 signing\_secret

从创建响应中获取 `signing_secret` 并安全存储，后续验证签名时使用。

### 步骤 3：实现接收端

在您的服务中实现 Webhook 接收端点，确保：

1. 验证 `Webhook-Signature` 签名
2. 使用事件 `id` 做幂等去重
3. 返回 `200 OK` 确认收到
4. 异步处理业务逻辑（避免超时）

### 步骤 4：发送测试事件

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/webhook_endpoints/{id}/test \
  -H "Authorization: Bearer $PAT"
```

### 步骤 5：验证并上线

确认测试事件接收正常后，即可投入生产使用。

***

## 附录 B：最佳实践

| 实践    | 说明                                |
| ----- | --------------------------------- |
| 签名验证  | 始终验证 `Webhook-Signature`，拒绝无效请求   |
| 幂等处理  | 使用事件 `id` 去重，防止重复消费               |
| 快速响应  | 接收端应在 5 秒内返回 2xx，耗时逻辑异步处理         |
| 时间戳校验 | 拒绝超过容忍窗口的事件，防止重放攻击                |
| 精确订阅  | 仅订阅需要的事件类型，减少不必要的网络开销             |
| 监控告警  | 监控 `consecutive_fail` 指标，及时发现投递异常 |
