
# Webhooks API

Webhooks（网络钩子）允许平台在发生特定事件（如新增联系人、收到回复、预约成功等）时立即通知您的其他系统。此 API 用于管理**订阅**本身：即哪些 URL 接收哪些事件。关于如何接收和验证端点收到的负载，请参阅 [Webhooks](../integrations/webhooks.md)。

以下所有路径均相对于 API 基础 URL：

```
https://api.youraiconnector.com/v1
```

每个请求都必须经过身份验证。请参阅 [身份验证](authentication.md) 以了解四种支持的方法。此处的示例使用 `X-API-Key` 标头（以及一种用于 cURL 的查询参数形式）。

::: note
**注意：** 您的账户必须启用 Webhooks。如果未启用，这些端点将返回 `403`。
:::


---

## 如何寻址订阅

每个订阅都有一个 `id` 和一个可选的 `name`。两者均可用作路径中的 `{webhookId}`，用于更新、删除、测试、健康检查和重新启用操作。

> **建议优先使用名称。** 订阅 ID 是位置性的，因此在删除其他订阅后可能会发生变动。如果您在创建订阅时设置了稳定的 `name`，请通过名称进行寻址以避免意外情况。

---

## 列出订阅

`GET /webhooks`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/webhooks?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/webhooks",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**响应**

```json
{
  "success": true,
  "webhooks": [
    {
      "id": "0",
      "name": "Order updates hook",
      "url": "https://hooks.example.com/incoming",
      "subscribed_to": ["Contact Created", "Replies"],
      "subscribed_to_tags": [],
      "created_at": "2026-06-09T12:00:00.000Z",
      "signing_enabled": true,
      "signing_secret_created_at": "2026-07-15T09:30:00.000Z",
      "retries_enabled": true,
      "enabled": true,
      "apply_to_sub_accounts": false
    }
  ]
}
```

`signing_enabled` 和 `retries_enabled` 是针对每个订阅的可选功能，默认均为关闭状态，除非您手动开启。请参阅 [签名负载](#signed-payloads) 和 [重试机制](#retries)。

`apply_to_sub_accounts` 是代理机构继承的启用选项 — 请参阅 [一个订阅适用于所有客户账户](#one-subscription-for-all-client-accounts-agencies)。默认情况下处于关闭状态，且在没有客户账户的账户上无效。

`enabled` 是订阅的开关 —— 请参阅 [关闭订阅](#switching-a-subscription-off)。已关闭的订阅仍会在此处列出。

签名密钥本身不会包含在此处 —— 请从 [`GET /webhooks/{id}/signing-secret`](#read-the-signing-secret) 中读取。

---

## 列出可订阅的事件类型

返回您可以在 `subscribed_to` 中使用的确切字符串。请使用此接口来发现有效的事件名称，而不是对其进行硬编码。

`GET /webhooks/events`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/webhooks/events" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/events", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/webhooks/events",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**响应**

响应为 `{"success": true, "events": [...]}`，其中 `events` 目前包含 22 个确切字符串：Contact Created、Human Alerted、Appointment Booked、Replies、Reads、Deliveries、Credits Spent、Credits Recharged、Low Credit Balance、Contact Paused、Contact Do Not Disturb、Contact Unarchived、New Message、Contact Resumed、Chat Concluded、Task Created、Task Updated、Task Completed、Daily Summary Created、Channel Connected、Broadcast Started 和 Broadcast Completed（`subscribed_to` 接受 Channel Connected，但目前没有任何内容会触发它，因此请勿基于此进行开发）。

有关每个事件的含义及其在有效负载中发送的 `event` 代码，请参阅 [22 个 Webhook 事件](../integrations/webhooks.md#the-22-webhook-events)。此端点是任何时刻的权威列表 — 请实时读取它，而不是硬编码名称。

---

## 创建订阅

`POST /webhooks`

| 字段 | 必填 | 说明 |
|---|---|---|
| `url` | 是 | 将通过 `POST` 接收事件有效负载的 HTTPS URL。必须是可公开访问的。 |
| `subscribed_to` | 是 | 一个非空的事件名称数组（请参阅 `/webhooks/events`）。 |
| `name` | 否 | 显示名称。稍后也可用作 `{webhookId}`。默认为带时间戳的名称。 |
| `subscribed_to_tags` | 否 | 用于缩小哪些标签会产生对话摘要通知的标签 ID。它不会将订阅的事件范围限定为这些标签 — 若要在应用特定标签时获取请求，请在代理（或活动）的 **标签** 选项卡中设置 Webhook URL。 |
| `retries_enabled` | 否 | 布尔值，默认为 `false`。选择加入失败投递的 [重试](#retries) 功能。 |
| `generate_signing_secret` | 否 | 布尔值，默认为 `false`。随订阅一起生成 HMAC [签名密钥](#signed-payloads)。该密钥仅返回一次，作为响应中的顶级 `signing_secret`。 |
| `enabled` | 否 | 布尔值，默认为 `true`。传递 `false` 以在创建时关闭订阅。请参阅 [关闭订阅](#switching-a-subscription-off)。 |
| `apply_to_sub_accounts` | 否 | 布尔值，默认为 `false`。在代理机构账户上，`true` 会使此订阅同时接收来自每个客户账户的事件 — 请参阅 [一个订阅适用于所有客户账户](#one-subscription-for-all-client-accounts-agencies)。 |

> **URL 规则：** URL 必须使用 `https://` 且必须是公开可访问的。普通的 `http://`、`localhost`、私有网络地址以及平台内部地址将被拒绝，并返回 `400`。

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/webhooks?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/incoming",
    "subscribed_to": ["Contact Created", "Replies"],
    "name": "Order updates hook"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://hooks.example.com/incoming",
    subscribed_to: ["Contact Created", "Replies"],
    name: "Order updates hook",
  }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/webhooks",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://hooks.example.com/incoming",
        "subscribed_to": ["Contact Created", "Replies"],
        "name": "Order updates hook",
    },
)
data = res.json()
```

**响应**

```json
{
  "success": true,
  "webhook_id": "1",
  "webhook": {
    "id": "1",
    "name": "Order updates hook",
    "url": "https://hooks.example.com/incoming",
    "subscribed_to": ["Contact Created", "Replies"],
    "subscribed_to_tags": [],
    "created_at": "2026-06-09T12:00:00.000Z"
  }
}
```

---

## 更新订阅

提供至少一个 `url`、`subscribed_to`、`name`、`subscribed_to_tags`、`retries_enabled`、`enabled` 或 `apply_to_sub_accounts`。省略的字段将保留其当前值。`subscribed_to` 和 `subscribed_to_tags` 是替换项，而非合并项。

`PUT /webhooks/{webhookId}`

> 更新订阅不会影响其签名密钥——请通过[签名密钥路由](#signed-payloads)进行管理。

> 当 URL 更改时，新 URL 的投递功能会自动重新启用，从而使之前失败的端点获得一个新的开始。

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/Order%20updates%20hook" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/v2/incoming",
    "subscribed_to": ["Replies", "Chat Concluded"]
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  `https://api.youraiconnector.com/v1/webhooks/${encodeURIComponent("Order updates hook")}`,
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url: "https://hooks.example.com/v2/incoming",
      subscribed_to: ["Replies", "Chat Concluded"],
    }),
  }
);
const data = await res.json();
```

**Python**

```python
import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/webhooks/Order updates hook",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "url": "https://hooks.example.com/v2/incoming",
        "subscribed_to": ["Replies", "Chat Concluded"],
    },
)
data = res.json()
```

**响应**

```json
{
  "success": true,
  "webhook_id": "0",
  "webhook": {
    "id": "0",
    "name": "Order updates hook",
    "url": "https://hooks.example.com/v2/incoming",
    "subscribed_to": ["Replies", "Chat Concluded"],
    "subscribed_to_tags": [],
    "created_at": "2026-06-09T12:00:00.000Z"
  }
}
```

未知的 id 或名称将返回 `404` 以及 `{ "success": false, "error": "Webhook not found" }`。

---

## 删除订阅

移除订阅，使其 URL 停止接收负载。其投递健康计数器会被重置，因此稍后重新添加相同的 URL 时将从零开始记录。

`DELETE /webhooks/{webhookId}`

**cURL**

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/webhooks/0" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/webhooks/0",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**响应**

```json
{
  "success": true
}
```

---

## 发送测试负载

向订阅的 URL 发送示例负载，以便您可以端到端地验证您的接收端。您可以选择传入一个 `event` 来控制示例模拟的事件类型。测试交付绝不会影响订阅的健康计数器。

`POST /webhooks/{webhookId}/test`

响应总是返回 `200` 并通过 `delivered` 标志报告结果——测试失败**不会**返回错误状态。当 `delivered` 为 `false` 时，响应中会包含失败详情。

| 字段 | 必需 | 描述 |
|---|---|---|
| `event` | 否 | 要模拟的事件类型（必须是 `/webhooks/events` 之一）。默认为交付事件。 |

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/webhooks/0/test?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event": "Contact Created" }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0/test", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ event: "Contact Created" }),
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/webhooks/0/test",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"event": "Contact Created"},
)
data = res.json()
```

**响应**（已交付）

```json
{
  "success": true,
  "webhook_id": "0",
  "delivered": true
}
```

**响应**（已失败）

```json
{
  "success": true,
  "webhook_id": "0",
  "delivered": false,
  "failure_type": "permanent",
  "status_code": 404,
  "error_message": "Request failed with status code 404"
}
```

`failure_type` 是 `permanent`、`temporary`、`timeout`、`network` 或 `unknown` 之一。

---

## 检查交付健康状况

返回订阅 URL 的交付健康记录：已成功和失败的交付次数、交付是否因重复失败而暂停，以及最近一次失败的详情。当尚未尝试任何交付时，返回 `"health": null`。

`GET /webhooks/{webhookId}/health`

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/webhooks/0/health" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0/health", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/webhooks/0/health",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**响应**

```json
{
  "success": true,
  "webhook_id": "0",
  "url": "https://hooks.example.com/incoming",
  "health": {
    "consecutive_failures": 0,
    "total_failures": 2,
    "total_successes": 120,
    "is_disabled": false,
    "disabled_at": null,
    "disabled_reason": null,
    "last_failure": null,
    "last_success_at": "2026-06-09T12:00:00.000Z",
    "created_at": "2026-05-01T08:00:00.000Z",
    "updated_at": "2026-06-09T12:00:00.000Z"
  }
}
```

当 `is_disabled` 为 `true` 时，表示由于重复失败，向该 URL 的交付已被自动暂停。请修复您的接收端，然后重新启用它（见下文）。

---

## 重新启用交付

恢复因重复失败而被自动暂停 URL 的 Webhook 交付。这会重置暂停标志和失败计数器，但**不会**尝试进行交付——请在之后使用测试端点来确认您的接收端已恢复正常。

`POST /webhooks/{webhookId}/reenable`

**cURL**

```bash
curl -X POST "https://api.youraiconnector.com/v1/webhooks/0/reenable?apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.com/v1/webhooks/0/reenable", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/webhooks/0/reenable",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
```

**响应**

```json
{
  "success": true,
  "webhook_id": "0"
}
```

---

## 关闭订阅

`enabled` 是订阅自身的开关。将其关闭会停止投递，同时保持 URL、事件列表和签名密钥不变。

```bash
# Off
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'

# Back on
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'
```

- **缺失即表示开启。** 在此字段存在之前创建的订阅没有存储 `enabled` 值，并且会正常投递。`GET /webhooks` 总是返回一个具体的布尔值。
- 已关闭的订阅**仍然会被** `GET /webhooks` 列出 —— 这就是你找到它们并重新开启的方式。
- 在关闭开关之前排队的 [重试](#retries) 不会恢复：重试会在发送时重新读取订阅，如果订阅已关闭，则会丢弃该重试。
- 在关闭期间被抑制的任何内容在重新开启时都不会被重放。

> 这与重复失败后的自动禁用不同，后者由 [`GET /webhooks/{id}/health`](#check-delivery-health) 报告为 `is_disabled`，并可通过 [`POST /webhooks/{id}/reenable`](#re-enable-delivery) 清除。`enabled` 是账户的开关；`is_disabled` 是我们的开关。两者互不覆盖 —— 订阅必须同时处于开启状态且未被自动禁用才能进行投递。

---

## 一个订阅适用于所有客户账户（代理机构）

在代理机构账户上，在订阅上设置 `apply_to_sub_accounts: true`（在创建时或通过 `PUT`），它也会接收发生在每个客户账户上的事件 — 一个端点覆盖整个代理机构，无需在每个客户账户上重新创建订阅。

```bash
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"apply_to_sub_accounts": true}'
```

其工作方式如下：

- **`user` 块用于区分账户。** 每个有效负载的 `user` 块都会标识事件实际发生的账户，因此您的接收器可以按客户进行路由。
- **代理机构订阅自身的设置适用于所有地方。** 其事件列表、[签名密钥](#signed-payloads) 和 [重试](#retries) 选项也用于继承的投递。
- **客户账户自身对同一 URL 的订阅优先。** 如果客户账户有自己的订阅指向同一个 URL，则该订阅将用于该账户的事件 — 同一个事件永远不会被投递到同一个端点两次。
- **客户账户看不到它。** 继承的订阅不会出现在客户账户自己的 Webhook 列表中，且客户无法将其关闭 — 只有代理机构可以管理它们。
- **投递健康状况按客户账户跟踪。** 持续失败的端点会自动为投递失败的账户禁用，而不是为整个代理机构禁用。
- **`subscribed_to_tags` 不会继承。** 标签列表引用的是代理机构自己的标签，这些标签在客户账户上不存在 — 对话摘要缩小范围仅适用于代理机构自己的事件。
- **在其他地方无效。** 在没有客户账户的账户上，该标志可以正常存储但不起作用。

---

## 每次投递的标头

无论订阅是否已签名，每次投递都会发送以下三个标头：

| 标头 | 含义 |
|---|---|
| `X-Webhook-Delivery` | 逻辑事件的稳定 ID。在重试过程中保持不变——请据此进行去重。 |
| `X-Webhook-Attempt` | 基于 1 的尝试次数。 |
| `X-Webhook-Event` | 事件名称。 |

---

## 签名载荷

签名是可选的，默认关闭，并按订阅进行设置。当订阅拥有签名密钥时，每次投递除了发送所有投递都会包含的三个标头（`X-Webhook-Delivery`、`X-Webhook-Attempt` 和 `X-Webhook-Event`）外，还会额外携带两个标头：

| 标头 | 含义 |
|---|---|
| `X-Webhook-Signature` | `v1=<hex>` — 字符串 `"<timestamp>.<raw request body>"` 的 HMAC-SHA256 值，使用您在 `GET/POST/DELETE /v1/webhooks/{webhookId}/signing-secret` 处生成和轮换的每个 Webhook 的签名密钥进行加密。 |
| `X-Webhook-Timestamp` | 发送时间，Unix 秒数。绑定到签名中，因此无法独立更改。 |

要进行验证，请使用您的密钥对原始正文重新计算 HMAC-SHA256，并将其与标头进行比较。请针对**原始**请求正文进行验证。重新序列化已解析的 JSON 会更改字节并导致比较失败。拒绝时间戳超出新鲜度窗口（300 秒是一个合理的默认值）的投递以防止重放攻击，并使用时间安全函数进行比较。

请参阅 [签名载荷](../integrations/webhooks.md#signed-payloads-verifying-a-webhook-really-came-from-us) 以获取完整的 Node 和 Python 验证示例。

> **签名与 API 身份验证不同。** REST API 本身使用 API 密钥进行身份验证，而不是 OAuth（OAuth 2.1 确实存在于您注册为机器人工具的 MCP 服务器中），并且目前还没有官方的 npm 或 PyPI SDK 包——请使用任何 HTTP 客户端调用这些端点。

### 读取签名密钥

`GET /webhooks/{id}/signing-secret`

```bash
curl "https://api.youraiconnector.com/v1/webhooks/0/signing-secret?apiKey=YOUR_API_KEY"
```

**响应**

```json
{
  "success": true,
  "webhook_id": "0",
  "signing_enabled": true,
  "signing_secret": "whsec_1a2b3c...",
  "signing_secret_created_at": "2026-07-15T09:30:00.000Z"
}
```

当签名关闭时，`signing_enabled` 为 `false`，`signing_secret` 为 `null`。

### 生成或轮换签名密钥

`POST /webhooks/{id}/signing-secret`

创建一个密钥（开启签名）或替换现有密钥。返回新密钥。

```bash
curl -X POST "https://api.youraiconnector.com/v1/webhooks/0/signing-secret?apiKey=YOUR_API_KEY"
```

**响应**

```json
{
  "success": true,
  "webhook_id": "0",
  "signing_enabled": true,
  "signing_secret": "whsec_9f8e7d...",
  "signing_secret_created_at": "2026-07-15T10:00:00.000Z"
}
```

轮换立即生效 —— 下一次投递仅使用新密钥进行签名。在您将更改部署到在线端点时，请短暂地同时接受两个密钥。

您也可以在创建时通过将 `"generate_signing_secret": true` 传递给 `POST /webhooks` 来铸造密钥；响应中随后会包含一个顶层的 `signing_secret` 字段。

### 关闭签名

`DELETE /webhooks/{id}/signing-secret`

```bash
curl -X DELETE "https://api.youraiconnector.com/v1/webhooks/0/signing-secret?apiKey=YOUR_API_KEY"
```

**响应**

```json
{
  "success": true,
  "webhook_id": "0",
  "signing_enabled": false
}
```

> 所有三个签名密钥路由都需要集成 **edit**（编辑）权限，包括 `GET` —— 密钥是一种可以伪造交付的凭据，因此它不会暴露给只读角色。

---

## 重试

可选，默认关闭，通过 `POST /webhooks` 或 `PUT /webhooks/{id}` 上的 `retries_enabled` 布尔值按订阅进行设置。

```bash
curl -X PUT "https://api.youraiconnector.com/v1/webhooks/0?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"retries_enabled": true}'
```

启用后，失败的交付会在第一次尝试后的 **1分钟、5分钟、30分钟和2小时** 进行重试（总计约 2小时40分钟 的覆盖时间）。

- **重试：** 5xx 响应、超时和连接失败。
- **不重试：** 任何 4xx 错误。接收方拒绝了请求本身，因此原样重放只会再次导致拒绝。

重试可能会导致重复交付 —— 一个处理了事件但在响应前超时的端点会再次收到该事件。请根据 `X-Webhook-Delivery` 进行去重，该值在多次尝试中保持不变。这就是重试需要手动开启的原因。

[delivery-health](#check-delivery-health) 计数器统计的是整个交付过程，而不是每次尝试：只有在所有重试都耗尽后才会记录一次失败，因此启用重试不会导致自动禁用触发得更快。

---

## 错误

所有错误都使用标准信封：

```json
{
  "success": false,
  "error": "Webhook not found"
}
```

常见情况：不允许的 URL、空的/无效的 `subscribed_to` 或缺少字段会返回 `400`；未知的 ID 或名称会返回 `404`；而 `403` 表示您的账户未启用 Webhook。请参阅 [错误](errors-and-pagination.md) 获取完整列表。

---

## 后续步骤

- [Webhooks（接收负载）](../integrations/webhooks.md) —— 设置您的接收器并了解负载格式。
- [身份验证](authentication.md) —— 对请求进行身份验证的四种方式。
- [错误与速率限制](errors-and-pagination.md) —— 状态码和 300 次请求/分钟的限制。
