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

# External Agent

> Подключите собственного ИИ-агента к YCloud Inbox для получения сообщений клиентов, отправки ответов и передачи диалогов вашей команде.

<Note>
  External Agent доступен на тарифных планах **YCloud Pro и выше**.
</Note>

Подключите собственного ИИ-агента к YCloud в качестве **External Agent**. Ваш агент работает в вашей инфраструктуре и функционирует бок о бок с операторами в Inbox: он принимает сообщения клиентов, отправляет ответы, запрашивает историю переписки и передает диалоги сотрудникам.

YCloud отправляет события на ваш URL-адрес Webhook. Ваш сервис обрабатывает эти события и вызывает REST API Inbox для отправки ответа или управления диалогом. YCloud не обращается к вашему API инференса ИИ, а HTTP-ответ от вашего Webhook не отправляется клиенту.

## Перед началом работы

Подготовьте:

* Публично доступную конечную точку HTTPS, принимающую запросы JSON `POST`.
* Сервис, который проверяет подписи Webhook, сохраняет события, предотвращает дублирование обработки и запускает вашего ИИ-агента.
* Стандартный API-ключ аккаунта YCloud, хранящийся на вашем сервере. Ключи API приложений разработчика (Developer App) не поддерживаются этими конечными точками Inbox.
* Бизнес-номер WhatsApp, подключенный к YCloud, а также команду операторов или отдельного сотрудника для перевода обращений.

Используйте `https://api.ycloud.com` в качестве базового URL API.

## Подключение агента

### 1. Создайте External Agent

1. В консоли YCloud перейдите в раздел **AI Agent** > **AI Agent**.
2. Выберите **Create Agent** > **External Agent**.
3. Укажите **Name** и ваш **Webhook URL**, например `https://agent.example.com/webhooks/ycloud`.
4. Нажмите **Save**.
5. Скопируйте сгенерированный **Bot ID** из параметров агента. Используйте это значение как `botId` в запросах к API; передавать идентификатор из вашей собственной ИИ-системы не требуется.

<Frame caption="Create an External Agent with a name and a webhook URL. This example shows the form before saving.">
  <img src="https://mintcdn.com/lchnan/gMy2vRUEvLE3uccm/product-assets/external-agent-2026-10-20/create-external-agent.png?fit=max&auto=format&n=gMy2vRUEvLE3uccm&q=85&s=b9098d53ba0ff38823991500a9fb40b2" alt="Форма создания External Agent в YCloud с полями Name, Webhook URL и кнопкой Save." width="3024" height="1656" data-path="product-assets/external-agent-2026-10-20/create-external-agent.png" />
</Frame>

На странице сведений отображаются конечная точка Webhook, ее **Signing secret** и четыре типа событий. Используйте секрет подписи для проверки входящих событий. Выберите **View delivery logs** , чтобы проанализировать доставку Webhook. Эта же конечная точка доступна на странице **Webhooks** .

Храните секрет подписи конечной точки отдельно от вашего API-ключа. Секрет проверяет входящие события Webhook; `X-API-Key` аутентифицирует ваши исходящие REST-запросы. Bot ID является идентификатором, а не учетными данными.

### 2. Назначьте номер WhatsApp

Перейдите в **WhatsApp Manager** > **WhatsApp accounts**, откройте настройки бизнес-номера и выберите **Inbox** > **Assignment**. В блоке **Priority 3 Assign to** выберите **AI Agent**, а затем укажите своего агента в разделе **External Agent** списка агентов.

Проверьте приоритет правил: назначение ответственному за клиента сотруднику или последнему обрабатывавшему оператору может иметь более высокий приоритет. См. [Conversation assignment rules](/ru/documentation/channels/whatsapp-accounts-management/phone-number-management/conversation-assignment-rules).

Один External Agent может обрабатывать несколько номеров. Каждый номер связывается только с одним ботом одновременно. Управлять этими связями можно в настройках назначения номера.

### 3. Обработайте диалог

1. Клиент отправляет сообщение в WhatsApp на ваш бизнес-номер.
2. YCloud открывает или продолжает диалог в Inbox и применяет правила назначения для этого номера.
3. Когда ваш External Agent обрабатывает диалог, YCloud отправляет соответствующие события Webhook на его конечную точку.
4. Ваш сервис проверяет и сохраняет событие, возвращает `2xx` и обрабатывает входящие данные от клиента асинхронно.
5. Ваш сервис вызывает API Inbox для отправки ответа, перевода диалога или его закрытия.
6. Ваш сервис отслеживает статус доставки с помощью `whatsapp.message.updated`.

## Идентификаторы и их назначение

| Идентификатор | Назначение |
| - | - |
| `botId` | Идентификатор External Agent, сгенерированный YCloud. |
| `phoneNumberId` | Идентификатор бизнес-номера WhatsApp в YCloud. |
| `conversationId` | Непрерывное взаимодействие между клиентом и бизнес-номером. |
| `dialogId` | Одна сессия обслуживания, от открытия до закрытия. При передаче диалога этот идентификатор сохраняется; новое входящее сообщение после закрытия начинает новый диалог. |
| Webhook `id` | Непрозрачный идентификатор события, используемый для дедупликации. Он не определяет хронологический порядок событий. |
| `inboxMessage.id` / `inboxMessageId` | Идентификатор сообщения в Inbox, используемый для ссылок на ответы и сопоставления доставки. |
| `channelMessageId` / `wamid` | Идентификаторы сообщений канала и WhatsApp. Храните их отдельно от идентификаторов Inbox. |

Идентификаторы представляют собой строки. Метки времени используют формат UTC RFC 3339. Используйте `dialogId`, а не `conversationId`, в путях API для диалогов.

## Получение событий Webhook

Все четыре типа событий поступают на **один и тот же webhook URL**. Каждая специальная доставка External Agent включает `id`, `type`, `apiVersion`, `createTime` и `externalBotContext`, а также соответствующий бизнес-объект.

| Событие | Бизнес-объект | Что должен делать ваш сервис |
| - | - | - |
| `inbox.message.created` | `inboxMessage` | Обрабатывайте входящие сообщения клиента только тогда, когда `direction=inbound` и `actionable=true`. Синхронизируйте исходящие сообщения без запуска повторного ответа ИИ. |
| `inbox.conversation.assigned` | `inboxConversation` | Отслеживайте изменения назначения. Остановите обработку при передаче диалога вашим агентом; запрашивайте историю при взятии существующего диалога в работу. |
| `inbox.conversation.status_updated` | `inboxConversation` | Отслеживайте состояние диалога и останавливайте ожидающие задачи при его закрытии. |
| `whatsapp.message.updated` | `whatsappMessage` | Отслеживайте статус доставки или ошибку сообщения, отправленного вашим агентом. |

### Проверка, сохранение и подтверждение

Проверяйте заголовок `YCloud-Signature` с помощью секретного ключа подписи эндпоинта и **необработанного тела HTTP-запроса** перед парсингом JSON. Формат подписи:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
YCloud-Signature: t=UNIX_TIMESTAMP,s=SIGNATURE_HEX
signed_payload = timestamp + "." + raw_request_body
signature = hex(HMAC-SHA256(endpoint_secret, signed_payload))
```

Используйте сравнение с постоянным временем выполнения и проверяйте метку времени с разумным допуском, например пять минут. См. примеры проверки подписи в разделе [Реализация получателя Webhook](/ru/api-reference/guides/api-fundamentals/implement-a-webhook-receiver).

Сохраните событие и его состояние дедупликации, а затем незамедлительно верните любой успешный ответ `2xx`. Выполняйте инференс ИИ и другие ресурсоемкие операции асинхронно. Ответы, отличные от `2xx`, и таймауты могут приводить к ограниченному количеству повторных попыток.

Выполняйте дедупликацию по **`(botId, event.id)`** в рамках вашего аккаунта YCloud. Изменение назначения может отправлять уведомления как исходящему, так и входящему агентам; использование только глобального ID события может привести к отбрасыванию уведомления для одного из агентов. Сохраняйте состояние обработки, чтобы повторное событие не вызвало отправку второго ответа.

### Пример: сообщение клиента

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "evt_1001",
  "type": "inbox.message.created",
  "apiVersion": "v2",
  "createTime": "2026-10-20T08:00:00Z",
  "externalBotContext": {
    "botId": "bot_1001",
    "phoneNumberId": "phone_1001",
    "conversationId": "9001",
    "dialogId": "9002",
    "status": "open",
    "assignee": { "type": "external_bot", "id": "bot_1001" },
    "actionable": true
  },
  "inboxMessage": {
    "id": "9003",
    "direction": "inbound",
    "inbound": {
      "id": "wa_in_1001",
      "wamid": "wamid.DEMO_IN_1001",
      "type": "text",
      "sendTime": "2026-10-20T08:00:00Z",
      "text": { "body": "How can I change my booking?" }
    },
    "conversation": { "id": "9001", "dialog": { "id": "9002" } }
  }
}
```

Считывайте содержимое от клиента из `inboxMessage.inbound`. События создания исходящих сообщений используют вместо этого `inboxMessage.outbound`. Сохраняйте неизвестные типы сообщений и поля для совместимости, а не обрабатывайте их как текст.

### Решение об ответе

`externalBotContext` — это снимок состояния, сделанный в момент формирования уведомления. Поле `botId` определяет бота-получателя; `assignee` определяет фактического обработчика в этом снимке. После передачи диалога эти значения могут различаться.

Значение `actionable=true` означает, что в этот момент бот-получатель обрабатывал открытый диалог. Оно также может быть `true` в событии создания исходящего сообщения или в отчете о доставке.

<Warning>
  Не запускайте ответ ИИ исключительно на основании `actionable`. Проверяйте тип события и направление сообщения. Задержанное событие не гарантирует, что диалог все еще обрабатывается вашим агентом. Запрашивайте текущий диалог при необходимости и обрабатывайте ошибки владения при каждом запросе на запись.
</Warning>

События могут приходить неоднократно или в нарушенном порядке. Поле очередности событий отсутствует. Группируйте задачи по `dialogId` и избегайте повторной обработки одних и тех же входящих данных клиента. Событие первоначального назначения не должно отдельно вызывать второй ответ на первое сообщение клиента.

### Что происходит после передачи диалога или его закрытия?

Новые сообщения клиентов и ответы операторов больше не доставляются предыдущему External Agent, как только диалог переходит к человеку, попадает в очередь, остается без исполнителя или закрывается.

Предыдущий агент по-прежнему получает соответствующее уведомление о передаче диалога или его закрытии. Уведомления о создании сообщений и отчеты о доставке для сообщений, которые он уже отправил, остаются связанными с этим агентом-отправителем даже после передачи диалога или смены привязки номера.

Ранее созданные уведомления могут поступать с опозданием. Отменяйте ожидающие задачи при смене ответственного или закрытии диалога и не возобновляйте их только потому, что у старого события указано `actionable=true`.

Обычные эндпоинты webhook сохраняют существующее поведение подписки и формат полезной нагрузки. Специальная доставка External Agent добавляет поле `externalBotContext`; не требуйте наличия этого поля при обычных доставках `whatsapp.message.updated`.

## Использование Inbox API

Передавайте `X-API-Key` в каждом запросе и `Content-Type: application/json` в запросах `POST`. Во всех приведенных ниже примерах используются плейсхолдеры идентификаторов. Замените их значениями вашего агента и полученных событий.

| Операция | Метод и путь |
| - | - |
| Получить текущий диалог | `GET /v2/inbox/dialogs/{dialogId}` |
| Получить историю переписки | `GET /v2/inbox/dialogs/{dialogId}/messages` |
| Получить список целей для передачи диалога | `GET /v2/inbox/handoffTargets` |
| Отправить ответ | `POST /v2/inbox/dialogs/{dialogId}/messages` |
| Передать диалог | `POST /v2/inbox/dialogs/{dialogId}/handoff` |
| Закрыть диалог | `POST /v2/inbox/dialogs/{dialogId}/close` |

Успешные ответы возвращают бизнес-объект напрямую, без оболочки `data`. Операции записи проверяют, открыт ли диалог и обрабатывается ли он указанным ботом в данный момент. YCloud определяет отправителя и клиента по диалогу; не передавайте `tenantId`, `from`, `to`, `recipient`, `operatorId` или `source` для их переопределения.

### Получение текущего диалога

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl 'https://api.ycloud.com/v2/inbox/dialogs/9002?botId=bot_1001' \
  --header "X-API-Key: $YCLOUD_API_KEY"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "9002",
  "conversationId": "9001",
  "phoneNumberId": "phone_1001",
  "status": "open",
  "bot": { "id": "bot_1001", "name": "Booking assistant" },
  "assignee": { "type": "external_bot", "id": "bot_1001" },
  "assignmentOutcome": "assigned",
  "canAct": true
}
```

Проверьте `status`, `assignee` и `canAct`. Поле `bot` может указывать на последнего связанного внешнего бота и не является подтверждением текущего владения диалогом. `canAct` отражает разрешения запрашиваемого бота на момент запроса; последующая запись все равно выполняет свои собственные проверки.

### Получение истории переписки

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl 'https://api.ycloud.com/v2/inbox/dialogs/9002/messages?botId=bot_1001&limit=50' \
  --header "X-API-Key: $YCLOUD_API_KEY"
```

Ответ содержит `items` и `nextCursor`. Каждый элемент включает сообщение Inbox `id`, `direction`, `type`, `content` и `createTime`. Для текстового сообщения прочитайте `content.text.body`.

История включает видимые клиенту сообщения из **той же переписки**, возможно, из предыдущих диалогов. Собственное значение `dialogId` сообщения может отличаться от запрашиваемого диалога или отсутствовать в более старой истории. Внутренние заметки, приватные сводки передачи диалога и внутренние системные сообщения исключаются.

Сообщения возвращаются по возрастанию ID сообщения, от более старых к более новым. Опустите `after` в первом запросе; передавайте `nextCursor` без изменений в качестве `after` в последующих запросах, пока оно не станет `null`. Значение `limit` по умолчанию равно 50, допустимый диапазон — от 1 до 100. Список целей для передачи диалога использует ту же схему пагинации.

### Отправка ответа

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST 'https://api.ycloud.com/v2/inbox/dialogs/9002/messages' \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "botId": "bot_1001",
    "sourceEventId": "evt_1001",
    "replyToMessageId": "9003",
    "message": {
      "type": "text",
      "text": { "body": "Open your booking details and select Change booking." }
    }
  }'
```

Параметры `botId` и `message` обязательны. `sourceEventId` и `replyToMessageId` необязательны. Ссылка на ответ представляет собой **ID сообщения Inbox**, например `inboxMessage.id`; она не является `wamid`.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "inboxMessageId": "9004",
  "channelMessageId": "wa_out_1001",
  "status": "accepted"
}
```

Проверяйте `status`, даже когда HTTP возвращает `200`. `accepted` означает, что сообщение поступило в конвейер отправки, а не то, что оно дошло до клиента. Ответ `200` также может содержать `failed`; поле `channelMessageId` может быть еще недоступно.

Используйте `whatsapp.message.updated` для отслеживания последующих состояний, включая `sent`, `delivered`, `read`, `failed`, `expired` или `deleted`. Сопоставляйте `externalBotContext.inboxMessageId` в квитанции с `inboxMessageId` из ответа на отправку. Проверяйте `whatsappMessage.errorCode` и `errorMessage` на предмет ошибок.

#### Типы сообщений

Объект `message` использует стандартную модель сообщений WhatsApp: `type` плюс соответствующее поле с контентом. Примеры включают `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contacts`, `interactive`, `template` и `reaction`. Существующие требования WhatsApp к отправке сообщений и шаблонам по-прежнему действуют.

Например, замените объект `message` на:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "type": "image",
  "image": {
    "link": "https://cdn.example.com/booking-help.png",
    "caption": "Where to find your booking settings."
  }
}
```

Для медиафайлов укажите либо `id`, либо `link`. Сведения о структуре тела сообщения см. в разделе [Отправка сообщения WhatsApp](/ru/api-reference/guides/whatsapp-platform/send-whatsapp-message). Отправляйте ответ через указанный выше эндпоинт Inbox, чтобы он использовал проверки обработчика диалога и контекст Inbox. Не передавайте и не полагайтесь на `filterUnsubscribed` или `filterBlocked` в этом запросе на отправку сообщения Inbox.

Ответы клиентов могут содержать `interactive.button_reply`, `interactive.list_reply`, `interactive.nfm_reply` или `button`. Поле `response_json` в ответе Flow является строкой JSON. Входящий тип `button` не является исходящим типом сообщения для этого эндпоинта.

### Передача диалога человеку

Сначала запросите список доступных команд или операторов для бизнес-номера диалога:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl 'https://api.ycloud.com/v2/inbox/handoffTargets?phoneNumberId=phone_1001&type=team&limit=50' \
  --header "X-API-Key: $YCLOUD_API_KEY"
```

Используйте `type=agent`, чтобы вместо этого получить список участников. Каждый возвращенный целевой объект предоставляет `id` и `type` для запроса на передачу диалога. Необязательное поле `online` оператора отражает статус присутствия; оно не гарантирует немедленного назначения.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST 'https://api.ycloud.com/v2/inbox/dialogs/9002/handoff' \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
    "botId": "bot_1001",
    "sourceEventId": "evt_1001",
    "target": { "type": "team", "id": "team_1001" },
    "reason": "The customer requested human help.",
    "summary": "The customer needs to change a booking and requires assistance."
  }'
```

Вы должны указать `target` с `type` (`team` или `agent`) и `id`. Запасного варианта передачи человеку по умолчанию нет. Необязательное поле `reason` поддерживает до 256 символов; необязательное поле `summary` поддерживает до 4 000 символов и не отображается в видимой клиенту истории сообщений.

Ответ содержит `outcome` и обновленный `dialog`. Возможные результаты: `assigned`, `queued`, `team_unassigned` или `unassigned`. Проверяйте фактический результат, а не предполагайте, что человек принял диалог.

При передаче сохраняется тот же `dialogId`. Как только ваш агент теряет владение, прекратите отвечать или пытаться закрыть этот диалог.

### Закрытие решенного диалога

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST 'https://api.ycloud.com/v2/inbox/dialogs/9002/close' \
  --header "X-API-Key: $YCLOUD_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "botId": "bot_1001", "sourceEventId": "evt_1001" }'
```

Ответ содержит `dialog` с `status=closed` и `canAct=false`. Ваш агент не может продолжать отправку сообщений или передачу этого закрытого диалога. Последующее валидное сообщение от клиента начинает новый диалог и повторно запускает правила распределения для этого номера.

## Обработка ошибок и повторные попытки

В ответах с ошибками используется объект `error` с `status`, `code`, `message` и `requestId`. Сохраняйте ID запроса при обращении в службу поддержки YCloud.

| Код статуса HTTP / пример кода | Действие |
| - | - |
| `400 INVALID_ARGUMENT` | Исправьте поля запроса, включая отсутствующую цель передачи диалога. |
| `401` или `403 CREDENTIAL_TYPE_NOT_SUPPORTED` / `BOT_MISMATCH` | Проверьте API-ключ аккаунта, тип учетных данных, Bot ID и принадлежность к арендатору (tenant ownership). |
| `404 NOT_FOUND` | Проверьте идентификаторы и область действия арендатора. Если целевой объект передачи (handoff) больше недоступен, запросите список объектов заново. |
| `409 NOT_CURRENT_HANDLER` / `DIALOG_CLOSED` | Остановите устаревшую работу. Проверьте текущий диалог повторно вместо повторной попытки того же действия. |
| `429`, `503` или сетевой таймаут | Снизьте частоту запросов на чтение. Для операций записи проверьте состояние, историю и отчеты о доставке перед повторной попыткой, так как исходный запрос мог завершиться успешно. |

<Warning>
  `sourceEventId` и `X-Request-Id` поддерживают трассировку; ни один из них не является ключом идемпотентности. API не обеспечивает дедупликацию между запросами. Слепая повторная попытка после неоднозначного результата может привести к отправке дублирующего сообщения.
</Warning>

Отслеживайте ошибки Webhook в разделе **View delivery logs** . Сохраняйте идентификаторы событий, диалогов, сообщений, запросов и результаты обработки. Количество повторных попыток отправки Webhook ограничено, поэтому при необходимости используйте запросы диалогов и истории для восстановления контекста.

## Часто задаваемые вопросы

### Можно ли подключить агента, созданного на базе моего собственного ИИ-фреймворка?

Да. Ваш сервис должен принимать и верифицировать вебхуки YCloud, а также вызывать REST API Inbox. Вы сами определяете, как ваш сервис генерирует ответы, обращается к базе знаний и вызывает собственные инструменты.

### Будет ли мой агент получать все переписки Inbox?

Нет. Его выделенный эндпоинт получает события только в рамках назначенной ему зоны ответственности, а также соответствующие уведомления о назначении и закрытии и отчеты о доставке для отправленных им сообщений. Новые сообщения после передачи диалога человеку не пересылаются предыдущему агенту. Ранее сгенерированные события могут приходить с задержкой.

### Может ли мой агент использовать сообщения из предыдущих диалогов?

Да. Эндпоинт истории возвращает видимую клиенту историю переписки, включая более ранние диалоги. Используйте собственное значение `dialogId` каждого сообщения, если оно доступно; не предполагайте, что каждое возвращенное сообщение относится к текущей сессии обслуживания.

### Что произойдет, если я сменю агента, назначенного на номер?

Открытые диалоги, которые в данный момент обрабатываются ботом, асинхронно переназначаются в соответствии с правилами маршрутизации номера. Сменяемый агент получает `assignmentRole=outgoing` и должен прекратить работу. Новый агент получает `reason=phone_binding_changed` со значением `assignmentRole=incoming`; он должен запросить текущий диалог и историю, прежде чем продолжить.

Идентификатор диалога и история остаются неизменными. Диалоги, которые в данный момент обрабатываются оператором-человеком, не передаются новому боту при таком переназначении номера. После того как другой бот заменит его, прежний бот больше не сможет запрашивать этот диалог через данные эндпоинты.

### Становится ли ответ моего вебхука ответом клиенту?

Нет. Верните `2xx`, чтобы подтвердить получение события, а затем отправьте ответ клиенту через `POST /v2/inbox/dialogs/{dialogId}/messages`.

### Почему мой агент получает `actionable=true` для своего собственного ответа?

Это поле описывает, кто вел открытый диалог в момент генерации события. Поэтому события создания исходящих сообщений и отчеты о доставке могут содержать `actionable=true`. Запускайте стандартный ответ ИИ только для входящего события `inbox.message.created` и учитывайте изменения владельца активного диалога.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.