
# API Webhooks

Webhooks cho phép nền tảng thông báo cho các hệ thống khác của bạn ngay khi có sự kiện xảy ra — một liên hệ mới, một phản hồi, một cuộc hẹn đã đặt, và nhiều hơn nữa. API này quản lý chính các **đăng ký**: URL nào nhận sự kiện nào. Để biết cách nhận và xác minh các payload mà endpoint của bạn nhận được, hãy xem [Webhooks](../integrations/webhooks.md).

Tất cả các đường dẫn bên dưới đều tương đối so với URL cơ sở của API:

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

Mọi yêu cầu đều phải được xác thực. Xem [Xác thực](authentication.md) để biết bốn phương thức được chấp nhận. Các ví dụ ở đây sử dụng tiêu đề `X-API-Key` (và một dạng tham số truy vấn cho cURL).

::: note
**Lưu ý:** Webhook phải được bật cho tài khoản của bạn. Nếu chưa, các điểm cuối này sẽ trả về `403`.
:::


---

## Cách định địa chỉ cho các đăng ký

Mỗi đăng ký có một `id` và một `name` tùy chọn. Cả hai đều có thể được sử dụng làm `{webhookId}` trong đường dẫn để cập nhật, xóa, kiểm tra, kiểm tra trạng thái và kích hoạt lại.

> **Nên ưu tiên sử dụng tên.** ID đăng ký mang tính vị trí, vì vậy chúng có thể thay đổi sau khi một đăng ký khác bị xóa. Nếu bạn đặt một `name` ổn định khi tạo đăng ký, hãy định địa chỉ theo tên để tránh các bất ngờ.

---

## Liệt kê các đăng ký

`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()
```

**Phản hồi**

```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` và `retries_enabled` là các tùy chọn đăng ký theo gói, cả hai đều tắt trừ khi bạn bật chúng lên. Xem [Payload đã ký](#signed-payloads) và [Thử lại](#retries).

`apply_to_sub_accounts` là tùy chọn kế thừa đại lý — xem [Một gói đăng ký cho tất cả tài khoản khách hàng](#one-subscription-for-all-client-accounts-agencies). Mặc định là tắt và không hoạt động trên các tài khoản không có tài khoản khách hàng nào.

`enabled` là công tắc bật/tắt của gói đăng ký — xem [Tắt gói đăng ký](#switching-a-subscription-off). Các gói đăng ký đã tắt vẫn được liệt kê tại đây.

Bản thân bí mật ký (signing secret) không bao giờ được bao gồm ở đây — hãy đọc nó từ [`GET /webhooks/{id}/signing-secret`](#read-the-signing-secret).

---

## Liệt kê các loại sự kiện có thể đăng ký

Trả về các chuỗi chính xác mà bạn có thể sử dụng trong `subscribed_to`. Hãy sử dụng tính năng này để khám phá các tên sự kiện hợp lệ thay vì mã hóa cứng chúng.

`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()
```

**Phản hồi**

Phản hồi là `{"success": true, "events": [...]}`, trong đó `events` hiện đang chứa 22 chuỗi chính xác: 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, và Broadcast Completed (Channel Connected được chấp nhận trong `subscribed_to` nhưng hiện tại không có gì phát ra nó, vì vậy đừng xây dựng dựa trên nó).

Để biết ý nghĩa của từng sự kiện và mã `event` mà nó gửi trong payload, hãy xem [22 Sự kiện Webhook](../integrations/webhooks.md#the-22-webhook-events). Endpoint này là danh sách có thẩm quyền tại bất kỳ thời điểm nào — hãy đọc trực tiếp thay vì mã hóa cứng các tên.

---

## Tạo một đăng ký

`POST /webhooks`

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `url` | Có | URL HTTPS sẽ nhận payload sự kiện qua `POST`. Phải có thể truy cập công khai. |
| `subscribed_to` | Có | Một mảng không rỗng các tên sự kiện (xem `/webhooks/events`). |
| `name` | Không | Tên hiển thị. Cũng có thể sử dụng làm `{webhookId}` sau này. Mặc định là tên có dấu thời gian. |
| `subscribed_to_tags` | Không | ID thẻ giúp thu hẹp các thẻ nào tạo ra thông báo tóm tắt cuộc trò chuyện. Nó không giới hạn các sự kiện của gói đăng ký vào các thẻ đó — để nhận yêu cầu khi một thẻ cụ thể được áp dụng, hãy đặt URL webhook trên thẻ đó trong tab **Thẻ** của đại lý (hoặc chiến dịch). |
| `retries_enabled` | Không | Boolean, mặc định là `false`. Chọn tham gia [thử lại](#retries) các lần gửi thất bại. |
| `generate_signing_secret` | Không | Boolean, mặc định là `false`. Tạo một [bí mật ký](#signed-payloads) HMAC với gói đăng ký. Bí mật được trả về một lần, dưới dạng `signing_secret` cấp cao nhất trong phản hồi. |
| `enabled` | Không | Boolean, mặc định là `true`. Truyền `false` để tạo gói đăng ký ở trạng thái tắt. Xem [Tắt gói đăng ký](#switching-a-subscription-off). |
| `apply_to_sub_accounts` | Không | Boolean, mặc định là `false`. Trên tài khoản đại lý, `true` giúp gói đăng ký này cũng nhận được sự kiện từ mọi tài khoản khách hàng — xem [Một gói đăng ký cho tất cả tài khoản khách hàng](#one-subscription-for-all-client-accounts-agencies). |

> **Quy tắc URL:** URL phải sử dụng `https://` và có thể truy cập công khai. Các địa chỉ `http://`, `localhost` thuần túy, địa chỉ mạng nội bộ và địa chỉ nội bộ của nền tảng sẽ bị từ chối với mã `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()
```

**Phản hồi**

```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"
  }
}
```

---

## Cập nhật đăng ký

Cung cấp ít nhất một trong các trường `url`, `subscribed_to`, `name`, `subscribed_to_tags`, `retries_enabled`, `enabled` hoặc `apply_to_sub_accounts`. Các trường bị bỏ qua sẽ giữ nguyên giá trị hiện tại. `subscribed_to` và `subscribed_to_tags` là các thay thế, không phải hợp nhất.

`PUT /webhooks/{webhookId}`

> Việc cập nhật đăng ký không bao giờ làm ảnh hưởng đến bí mật ký của nó — hãy quản lý điều đó thông qua các [tuyến đường bí mật ký](#signed-payloads).

> Khi URL thay đổi, việc gửi dữ liệu cho URL mới sẽ tự động được kích hoạt lại, giúp một điểm cuối từng bị lỗi có một khởi đầu mới.

**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()
```

**Phản hồi**

```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"
  }
}
```

Một id hoặc tên không xác định sẽ trả về `404` kèm theo `{ "success": false, "error": "Webhook not found" }`.

---

## Xóa đăng ký

Xóa đăng ký để URL của nó ngừng nhận payload. Các bộ đếm trạng thái gửi dữ liệu của nó sẽ được đặt lại, vì vậy việc thêm lại cùng một URL sau này sẽ bắt đầu với một hồ sơ sạch.

`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()
```

**Phản hồi**

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

---

## Gửi payload kiểm thử

Gửi một payload mẫu đến URL của đăng ký để bạn có thể xác minh đầu thu của mình từ đầu đến cuối. Tùy chọn truyền vào một `event` để kiểm soát loại sự kiện mà mẫu sẽ mô phỏng. Các lần gửi kiểm thử không bao giờ ảnh hưởng đến bộ đếm trạng thái của đăng ký.

`POST /webhooks/{webhookId}/test`

Phản hồi luôn trả về `200` và báo cáo kết quả với cờ `delivered` — một bài kiểm thử thất bại **không** trả về trạng thái lỗi. Khi `delivered` là `false`, phản hồi sẽ bao gồm chi tiết về lỗi.

| Trường | Bắt buộc | Mô tả |
|---|---|---|
| `event` | Không | Loại sự kiện cần mô phỏng (phải là một trong các `/webhooks/events`). Mặc định là sự kiện gửi dữ liệu. |

**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()
```

**Phản hồi** (đã gửi)

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

**Phản hồi** (thất bại)

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

`failure_type` là một trong số `permanent`, `temporary`, `timeout`, `network`, hoặc `unknown`.

---

## Kiểm tra trạng thái gửi

Trả về bản ghi trạng thái gửi cho URL của đăng ký: số lượng lần gửi thành công và thất bại, liệu việc gửi có đang bị tạm dừng sau các lần thất bại liên tiếp hay không, và chi tiết về lần thất bại gần nhất. Trả về `"health": null` khi chưa có lần gửi nào được thực hiện.

`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()
```

**Phản hồi**

```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"
  }
}
```

Khi `is_disabled` là `true`, việc gửi đến URL đã bị tạm dừng tự động sau các lần thất bại liên tiếp. Hãy sửa bộ nhận của bạn, sau đó kích hoạt lại (bên dưới).

---

## Kích hoạt lại việc gửi

Tiếp tục gửi cho webhook có URL đã bị tạm dừng tự động sau các lần thất bại liên tiếp. Thao tác này sẽ đặt lại cờ tạm dừng và bộ đếm lỗi nhưng **không** thực hiện gửi lại — hãy sử dụng endpoint kiểm thử sau đó để xác nhận bộ nhận của bạn đã hoạt động bình thường trở lại.

`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()
```

**Phản hồi**

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

---

## Tắt gói đăng ký

`enabled` là công tắc bật/tắt của chính gói đăng ký. Việc tắt nó sẽ dừng gửi thông báo trong khi vẫn giữ nguyên URL, danh sách sự kiện và bí mật ký.

```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}'
```

- **Vắng mặt nghĩa là bật.** Một gói đăng ký được tạo trước khi trường này tồn tại sẽ không có giá trị `enabled` được lưu trữ và vẫn gửi thông báo bình thường. `GET /webhooks` luôn báo cáo một giá trị boolean cụ thể.
- Các gói đăng ký đã tắt **vẫn được liệt kê** bởi `GET /webhooks` — đó là cách bạn tìm thấy chúng để bật lại.
- Một lần [thử lại](#retries) được xếp hàng trước khi tắt sẽ không tiếp tục: quá trình thử lại sẽ đọc lại gói đăng ký tại thời điểm gửi và hủy nếu nó đang ở trạng thái tắt.
- Không có gì bị chặn trong khi tắt được phát lại khi bạn bật lại.

> Khác biệt với việc tự động vô hiệu hóa sau nhiều lần thất bại liên tiếp, được báo cáo bởi [`GET /webhooks/{id}/health`](#check-delivery-health) dưới dạng `is_disabled` và được xóa bằng [`POST /webhooks/{id}/reenable`](#re-enable-delivery). `enabled` là công tắc của tài khoản; `is_disabled` là công tắc của chúng tôi. Không cái nào ghi đè cái nào — một gói đăng ký phải vừa được bật vừa không bị tự động vô hiệu hóa thì mới có thể gửi thông báo.

---

## Một gói đăng ký cho tất cả tài khoản khách hàng (đại lý)

Trên tài khoản đại lý, hãy đặt `apply_to_sub_accounts: true` trên gói đăng ký (tại thời điểm tạo hoặc qua `PUT`) và nó cũng sẽ nhận được các sự kiện xảy ra trên mọi tài khoản khách hàng của đại lý — một endpoint bao quát toàn bộ đại lý, thay vì phải tạo lại gói đăng ký trên từng tài khoản khách hàng.

```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}'
```

Cách thức hoạt động:

- **Khối `user` giúp phân biệt các tài khoản.** Khối `user` của mỗi payload xác định tài khoản nơi sự kiện thực sự xảy ra, vì vậy trình nhận của bạn có thể định tuyến theo từng khách hàng.
- **Các cài đặt riêng của gói đăng ký đại lý được áp dụng ở mọi nơi.** Danh sách sự kiện, [bí mật ký](#signed-payloads) và tùy chọn [thử lại](#retries) của nó cũng được sử dụng cho các lần gửi được kế thừa.
- **Gói đăng ký riêng của tài khoản khách hàng cho cùng một URL sẽ được ưu tiên.** Nếu một tài khoản khách hàng có gói đăng ký riêng trỏ đến cùng một URL, gói đó sẽ được sử dụng cho các sự kiện của tài khoản đó — cùng một sự kiện không bao giờ được gửi hai lần đến một endpoint.
- **Tài khoản khách hàng không nhìn thấy nó.** Các gói đăng ký được kế thừa không xuất hiện trong danh sách webhook riêng của tài khoản khách hàng và khách hàng không thể tắt chúng — chỉ đại lý mới quản lý được.
- **Tình trạng gửi được theo dõi theo từng tài khoản khách hàng.** Một endpoint liên tục thất bại sẽ tự động bị vô hiệu hóa đối với tài khoản có lần gửi thất bại, không phải cho toàn bộ đại lý.
- **`subscribed_to_tags` không được kế thừa.** Danh sách thẻ tham chiếu đến các thẻ riêng của đại lý, vốn không tồn tại trên các tài khoản khách hàng — việc thu hẹp tóm tắt cuộc trò chuyện chỉ áp dụng cho các sự kiện riêng của đại lý.
- **Không hoạt động ở nơi khác.** Trên tài khoản không có tài khoản khách hàng nào, cờ này được lưu trữ bình thường và không có tác dụng gì.

---

## Các tiêu đề trên mỗi lần gửi

Ba tiêu đề này được gửi trong mỗi lần gửi, bất kể đăng ký có được ký hay không:

| Tiêu đề | Ý nghĩa |
|---|---|
| `X-Webhook-Delivery` | ID ổn định cho sự kiện logic. Giống hệt nhau giữa các lần thử lại — hãy khử trùng lặp dựa trên nó. |
| `X-Webhook-Attempt` | Số lần thử bắt đầu từ 1. |
| `X-Webhook-Event` | Tên sự kiện. |

---

## Payload đã ký

Việc ký là tùy chọn, mặc định là tắt và được thiết lập cho mỗi đăng ký. Khi một đăng ký có bí mật ký, mỗi lần gửi sẽ mang thêm hai tiêu đề nữa ngoài ba tiêu đề được gửi trên mỗi lần gửi (`X-Webhook-Delivery`, `X-Webhook-Attempt` và `X-Webhook-Event`):

| Tiêu đề | Ý nghĩa |
|---|---|
| `X-Webhook-Signature` | `v1=<hex>` — HMAC-SHA256 của chuỗi `"<timestamp>.<raw request body>"`, được khóa bằng bí mật ký cho mỗi webhook mà bạn tạo và xoay vòng tại `GET/POST/DELETE /v1/webhooks/{webhookId}/signing-secret`. |
| `X-Webhook-Timestamp` | Thời gian gửi, tính bằng giây Unix. Được gắn vào chữ ký, vì vậy nó không thể bị thay đổi độc lập. |

Để xác minh, hãy tính toán lại HMAC-SHA256 trên nội dung thô bằng bí mật của bạn và so sánh nó với tiêu đề. Xác minh dựa trên nội dung yêu cầu **thô**. Việc tuần tự hóa lại JSON đã phân tích cú pháp sẽ làm thay đổi các byte và phá vỡ quá trình so sánh. Hãy từ chối các lần gửi có dấu thời gian nằm ngoài cửa sổ mới (300 giây là mặc định hợp lý) để ngăn chặn phát lại và so sánh bằng hàm an toàn về thời gian.

Xem [Payload đã ký](../integrations/webhooks.md#signed-payloads-verifying-a-webhook-really-came-from-us) để biết các ví dụ xác minh đầy đủ bằng Node và Python.

> **Ký không giống như xác thực API.** Bản thân REST API xác thực bằng khóa API thay vì OAuth (OAuth 2.1 tồn tại cho các máy chủ MCP mà bạn đăng ký làm công cụ bot), và hiện chưa có gói SDK npm hoặc PyPI chính thức nào — hãy gọi các endpoint bằng bất kỳ trình khách HTTP nào.

### Đọc bí mật ký

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

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

**Phản hồi**

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

Khi tính năng ký bị tắt, `signing_enabled` là `false` và `signing_secret` là `null`.

### Tạo hoặc xoay vòng bí mật ký

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

Tạo một bí mật (bật tính năng ký) hoặc thay thế bí mật hiện có. Trả về bí mật mới.

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

**Phản hồi**

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

Việc xoay vòng có hiệu lực ngay lập tức — lần gửi tiếp theo chỉ được ký bằng bí mật mới. Hãy chấp nhận cả hai bí mật trong thời gian ngắn trong khi bạn triển khai thay đổi tới điểm cuối trực tiếp.

Bạn cũng có thể tạo bí mật tại thời điểm tạo bằng cách truyền `"generate_signing_secret": true` vào `POST /webhooks`; phản hồi sau đó sẽ bao gồm trường `signing_secret` ở cấp cao nhất.

### Tắt tính năng ký

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

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

**Phản hồi**

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

> Tất cả ba tuyến đường bí mật ký đều yêu cầu quyền **chỉnh sửa** Tích hợp, bao gồm cả `GET` — bí mật là thông tin xác thực có thể giả mạo các lần gửi, vì vậy nó không được hiển thị cho các vai trò chỉ đọc.

---

## Thử lại

Tùy chọn, mặc định là tắt và được thiết lập cho mỗi đăng ký thông qua boolean `retries_enabled` trên `POST /webhooks` hoặc `PUT /webhooks/{id}`.

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

Khi được bật, một lần gửi thất bại sẽ được thử lại sau **1 phút, 5 phút, 30 phút và 2 giờ** kể từ lần thử đầu tiên (tổng cộng khoảng 2 giờ 40 phút).

- **Được thử lại:** các phản hồi 5xx, hết thời gian chờ và lỗi kết nối.
- **Không được thử lại:** bất kỳ phản hồi 4xx nào. Người nhận đang tự từ chối yêu cầu, vì vậy việc phát lại yêu cầu đó mà không thay đổi chỉ lặp lại sự từ chối.

Việc thử lại có thể dẫn đến phân phối trùng lặp — một endpoint đã xử lý sự kiện nhưng hết thời gian chờ trước khi phản hồi sẽ nhận lại sự kiện đó. Hãy khử trùng lặp dựa trên `X-Webhook-Delivery`, vốn không đổi qua các lần thử. Đây là lý do tại sao việc thử lại là tùy chọn (opt-in).

Các bộ đếm [delivery-health](#check-delivery-health) đếm toàn bộ một lần gửi, không phải từng lần thử: lỗi chỉ được ghi lại một lần sau khi tất cả các lần thử đã cạn kiệt, vì vậy việc bật thử lại không làm cho trình kích hoạt tự động vô hiệu hóa xảy ra sớm hơn.

---

## Lỗi

Tất cả các lỗi đều sử dụng cấu trúc chuẩn:

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

Các trường hợp phổ biến: URL không được phép, `subscribed_to` trống/không hợp lệ, hoặc thiếu các trường sẽ trả về `400`; id hoặc tên không xác định sẽ trả về `404`; và `403` có nghĩa là webhook chưa được bật cho tài khoản của bạn. Xem [Lỗi](errors-and-pagination.md) để biết danh sách đầy đủ.

---

## Các bước tiếp theo

- [Webhooks (nhận payload)](../integrations/webhooks.md) — thiết lập bộ thu của bạn và tìm hiểu cấu trúc payload.
- [Xác thực](authentication.md) — bốn cách để xác thực một yêu cầu.
- [Lỗi & Giới hạn tốc độ](errors-and-pagination.md) — các mã trạng thái và giới hạn 300 yêu cầu/phút.
