> ## 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

> Conecte seu próprio agente de IA ao YCloud Inbox para receber mensagens de clientes, enviar respostas e transferir conversas para a sua equipe.

<Note>
  O External Agent está disponível nos **planos YCloud Pro e superiores**.
</Note>

Conecte seu próprio agente de IA à YCloud como um **External Agent**. O seu agente é executado na sua infraestrutura e trabalha em conjunto com os seus atendentes humanos no Inbox: ele recebe mensagens de clientes, envia respostas, recupera o histórico da conversa e transfere conversas para a sua equipe.

A YCloud envia eventos para a sua URL de webhook. O seu serviço processa esses eventos e chama a API REST do Inbox para responder ou gerenciar a conversa. A YCloud não chama a sua API de inferência de IA, e a resposta HTTP do seu webhook não é enviada ao cliente.

## Antes de começar

Prepare:

* Um endpoint HTTPS acessível publicamente que aceite solicitações JSON `POST`.
* Um serviço que verifique assinaturas de webhook, armazene eventos, evite processamento duplicado e execute seu agente de IA.
* Uma chave de API de conta YCloud normal, armazenada no seu servidor. Chaves de API de Aplicativos de Desenvolvedor não são suportadas por esses endpoints do Inbox.
* Um número comercial do WhatsApp conectado à YCloud e uma equipe ou atendente humano para receber transferências.

Use `https://api.ycloud.com` como a URL base da API.

## Conecte seu agente

### 1. Criar um External Agent

1. No console da YCloud, acesse **AI Agent** > **AI Agent**.
2. Selecione **Create Agent** > **External Agent**.
3. Insira um **Name** e a sua **Webhook URL**, como `https://agent.example.com/webhooks/ycloud`.
4. Selecione **Save**.
5. Copie o **Bot ID** gerado nos detalhes do agente. Use esse valor como `botId` em solicitações à API; não é necessário fornecer um ID do seu próprio sistema de IA.

<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="Formulário de criação de External Agent da YCloud com os controles Name, Webhook URL e Save." width="3024" height="1656" data-path="product-assets/external-agent-2026-10-20/create-external-agent.png" />
</Frame>

A página de detalhes exibe o endpoint de webhook, o seu **Signing secret** e os quatro tipos de evento. Use o signing secret para verificar eventos recebidos. Selecione **View delivery logs** para investigar o envio dos webhooks. O mesmo endpoint também está disponível na página **Webhooks** .

Mantenha o signing secret do endpoint separado da sua chave de API. O secret verifica webhooks recebidos; `X-API-Key` autentica as suas solicitações REST de saída. Um Bot ID é um identificador, não uma credencial.

### 2. Atribuir um número do WhatsApp

Abra **WhatsApp Manager** > **WhatsApp accounts**, abra as configurações do número comercial e selecione **Inbox** > **Assignment**. Em **Priority 3 Assign to**, selecione **AI Agent** e, em seguida, escolha o seu agente na seção **External Agent** do seletor de agentes.

Verifique a prioridade das regras: a atribuição ao responsável pelo cliente ou ao último atendente a cuidar do caso pode ter precedência. Consulte [Conversation assignment rules](/pt/documentation/channels/whatsapp-accounts-management/phone-number-management/conversation-assignment-rules).

Um External Agent pode lidar com vários números. Cada número fica associado a um bot por vez. Gerencie essas associações nas configurações de atribuição do número.

### 3. Processar uma conversa

1. Um cliente envia uma mensagem de WhatsApp para o seu número comercial.
2. A YCloud abre ou continua um diálogo no Inbox e aplica as regras de atribuição do número.
3. Quando o seu External Agent gerencia o diálogo, a YCloud entrega os eventos de webhook aplicáveis ao endpoint dele.
4. Seu serviço verifica e armazena o evento, retorna `2xx` e processa a entrada do cliente de forma assíncrona.
5. Seu serviço chama a API do Inbox para responder, transferir ou encerrar o diálogo.
6. Seu serviço monitora a entrega por meio de `whatsapp.message.updated`.

## Entenda os identificadores

| Identificador | Finalidade |
| - | - |
| `botId` | O ID do External Agent gerado pela YCloud. |
| `phoneNumberId` | O ID do número comercial do WhatsApp na YCloud. |
| `conversationId` | A relação contínua entre um cliente e um número comercial. |
| `dialogId` | Uma sessão de atendimento, desde a abertura até o encerramento. Uma transferência mantém esse ID; uma nova mensagem recebida válida após o encerramento inicia um novo diálogo. |
| Webhook `id` | Um ID de evento opaco usado para desduplicação. Ele não codifica a ordem dos eventos. |
| `inboxMessage.id` / `inboxMessageId` | O ID da mensagem do Inbox, usado para referências de resposta e correlação de entrega. |
| `channelMessageId` / `wamid` | Identificadores de mensagem do canal e do WhatsApp. Armazene-os separadamente dos IDs do Inbox. |

Os IDs são strings. Os carimbos de data/hora usam UTC RFC 3339. Use `dialogId`, não `conversationId`, nos caminhos da API de diálogo.

## Receber eventos de webhook

Todos os quatro tipos de eventos chegam à **mesma URL de webhook**. Cada entrega dedicada do External Agent inclui `id`, `type`, `apiVersion`, `createTime` e `externalBotContext`, além do objeto de negócio correspondente.

| Evento | Objeto de negócio | O que seu serviço deve fazer |
| - | - | - |
| `inbox.message.created` | `inboxMessage` | Processe a entrada do cliente somente quando `direction=inbound` e `actionable=true`. Sincronize mensagens de saída sem acionar outra resposta de IA. |
| `inbox.conversation.assigned` | `inboxConversation` | Monitore alterações de atribuição. Interrompa o processamento quando seu agente fizer o transbordo; recupere o histórico quando ele assumir um diálogo existente. |
| `inbox.conversation.status_updated` | `inboxConversation` | Monitore o estado do diálogo e interrompa o trabalho pendente quando ele for encerrado. |
| `whatsapp.message.updated` | `whatsappMessage` | Monitore o estado de entrega ou falha de uma mensagem enviada pelo seu agente. |

### Verificar, armazenar e confirmar

Verifique o cabeçalho `YCloud-Signature` usando o segredo de assinatura do endpoint e o **corpo bruto da requisição HTTP**, antes de analisar o JSON. O formato da assinatura é:

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

Use uma comparação de tempo constante e valide o timestamp com uma tolerância razoável, como cinco minutos. Consulte [Implementar um receptor de webhook](/pt/api-reference/guides/api-fundamentals/implement-a-webhook-receiver) para ver exemplos de verificação de assinatura.

Persista o evento e seu estado de desduplicação e, em seguida, retorne qualquer resposta `2xx` bem-sucedida prontamente. Execute a inferência de IA e outros trabalhos lentos de forma assíncrona. Respostas que não sejam `2xx` e tempos limites esgotados (timeouts) podem acionar um número limitado de novas tentativas.

Desduplique por **`(botId, event.id)`**, dentro da sua conta da YCloud. Uma alteração de atribuição pode notificar tanto o agente que sai quanto o que entra; usar apenas um ID de evento global pode descartar a notificação de um dos agentes. Mantenha o estado de processamento para que um evento repetido não envie uma segunda resposta.

### Exemplo: mensagem do cliente

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

Leia o conteúdo do cliente a partir de `inboxMessage.inbound`. Eventos de criação de saída usam `inboxMessage.outbound`. Mantenha tipos de mensagens e campos desconhecidos para compatibilidade, em vez de tratá-los como texto.

### Decidir se deve responder

`externalBotContext` é um snapshot criado quando a notificação é gerada. Seu `botId` identifica o bot receptor; `assignee` identifica o manipulador real nesse snapshot. Eles podem diferir após um transbordo.

`actionable=true` significa que o bot receptor estava manipulando um diálogo aberto naquele momento. Ele também pode ser `true` em um evento de criação de saída ou em um recibo de entrega.

<Warning>
  Não acione uma resposta de IA apenas com base em `actionable`. Verifique o tipo de evento e a direção da mensagem. Um evento atrasado não prova que seu agente ainda manipula o diálogo. Consulte o diálogo atual quando necessário e trate erros de propriedade em todas as requisições de gravação.
</Warning>

Os eventos podem chegar mais de uma vez ou fora de ordem. Não há campo de sequência de eventos. Agrupe o trabalho por `dialogId` e evite processar a mesma entrada do cliente duas vezes. Um evento inicial de atribuição não deve acionar de forma independente uma segunda resposta à mensagem inicial do cliente.

### O que acontece após um transbordo ou encerramento?

Novas mensagens do cliente e respostas humanas não são mais entregues ao External Agent anterior assim que o diálogo passa a ser manipulado por um humano, entra na fila, fica não atribuído ou é encerrado.

O agente anterior ainda recebe sua notificação aplicável de saída de atribuição ou encerramento. Notificações de criação e recibos de entrega de mensagens que ele já enviou continuam associados a esse agente remetente, mesmo após um transbordo ou reatribuição de número.

Notificações geradas anteriormente podem chegar com atraso. Cancele o trabalho pendente quando a titularidade mudar ou quando o diálogo for encerrado, e não o retome apenas porque um evento antigo contém `actionable=true`.

Endpoints de webhook comuns mantêm seu comportamento de assinatura e formato de payload existentes. A entrega dedicada do External Agent adiciona `externalBotContext`; não exija esse campo em entregas comuns de `whatsapp.message.updated`.

## Usar a API Inbox

Envie `X-API-Key` em todas as requisições e `Content-Type: application/json` em requisições `POST`. Todos os exemplos abaixo usam IDs de espaço reservado. Substitua-os pelos valores do seu agente e dos eventos recebidos.

| Operação | Método e caminho |
| - | - |
| Obter diálogo atual | `GET /v2/inbox/dialogs/{dialogId}` |
| Obter histórico de conversa | `GET /v2/inbox/dialogs/{dialogId}/messages` |
| Listar alvos de transbordo | `GET /v2/inbox/handoffTargets` |
| Enviar uma resposta | `POST /v2/inbox/dialogs/{dialogId}/messages` |
| Fazer transbordo | `POST /v2/inbox/dialogs/{dialogId}/handoff` |
| Encerrar o diálogo | `POST /v2/inbox/dialogs/{dialogId}/close` |

As respostas com sucesso retornam o objeto de negócios diretamente, sem um wrapper `data`. As operações de gravação verificam se a caixa de diálogo está aberta e sendo tratada atualmente pelo bot especificado. A YCloud determina o remetente e o cliente a partir da caixa de diálogo; não envie `tenantId`, `from`, `to`, `recipient`, `operatorId` ou `source` para substituí-los.

### Obter a caixa de diálogo atual

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

Verifique `status`, `assignee` e `canAct`. O campo `bot` pode fazer referência ao último bot externo associado e não é uma prova de propriedade atual. `canAct` reflete a permissão do bot consultado no momento da solicitação; uma gravação posterior ainda realiza suas próprias verificações.

### Recuperar o histórico de conversas

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

A resposta contém `items` e `nextCursor`. Cada item inclui uma mensagem do Inbox `id`, `direction`, `type`, `content` e `createTime`. Para uma mensagem de texto, leia `content.text.body`.

O histórico inclui mensagens visíveis para o cliente da **mesma conversa**, potencialmente abrangendo caixas de diálogo anteriores. O próprio `dialogId` de uma mensagem pode ser diferente da caixa de diálogo solicitada ou estar ausente em históricos mais antigos. Notas internas, resumos de transferência privada e mensagens internas do sistema são excluídos.

As mensagens são retornadas em ordem crescente de ID de mensagem, da mais antiga para a mais recente. Omita `after` na primeira solicitação; transmita `nextCursor` inalterado como `after` em solicitações subsequentes até que seja `null`. O padrão de `limit` é 50, com um intervalo de 1 a 100. A lista de alvos de transferência usa o mesmo padrão de paginação.

### Enviar uma resposta

```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` e `message` são obrigatórios. `sourceEventId` e `replyToMessageId` são opcionais. A referência de resposta é um **ID de mensagem do Inbox**, como `inboxMessage.id`; não é um `wamid`.

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

Verifique `status` mesmo quando o HTTP retornar `200`. `accepted` significa que a mensagem entrou no pipeline de envio, e não que chegou ao cliente. Uma resposta `200` também pode relatar `failed`; `channelMessageId` pode ainda não estar disponível.

Use `whatsapp.message.updated` para acompanhar estados subsequentes, incluindo `sent`, `delivered`, `read`, `failed`, `expired` ou `deleted`. Correlacione o `externalBotContext.inboxMessageId` da confirmação com o `inboxMessageId` da resposta de envio. Inspecione `whatsappMessage.errorCode` e `errorMessage` em caso de falhas.

#### Tipos de mensagem

O objeto `message` usa o modelo padrão de mensagens do WhatsApp: `type` mais o campo de conteúdo correspondente. Exemplos incluem `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contacts`, `interactive`, `template` e `reaction`. Os requisitos existentes de envio e de modelo de mensagem do WhatsApp continuam sendo aplicados.

Por exemplo, substitua o objeto `message` por:

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

Para mídia, forneça `id` ou `link`. Para estruturas de corpo de mensagem, consulte [Enviar uma mensagem do WhatsApp](/pt/api-reference/guides/whatsapp-platform/send-whatsapp-message). Envie a resposta por meio do endpoint do Inbox acima para que ele utilize as verificações de manipulador da caixa de diálogo e o contexto do Inbox. Não envie nem dependa de `filterUnsubscribed` ou `filterBlocked` nesta solicitação de mensagem do Inbox.

As respostas do cliente podem incluir `interactive.button_reply`, `interactive.list_reply`, `interactive.nfm_reply` ou `button`. O campo `response_json` de uma resposta de Flow é uma string JSON. O tipo de entrada `button` não é um tipo de mensagem de saída para este endpoint.

### Transferir para um atendente humano

Primeiro, liste as equipes ou agentes disponíveis para o número de negócios da caixa de diálogo:

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

Em vez disso, use `type=agent` para listar os membros. Cada destino retornado fornece o `id` e o `type` para a solicitação de transferência. O campo opcional `online` de um agente descreve a presença; ele não garante a atribuição imediata.

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

Você deve especificar um `target` com `type` (`team` ou `agent`) e `id`. Não há fallback humano padrão. O campo opcional `reason` aceita até 256 caracteres; o campo opcional `summary` aceita até 4.000 caracteres e não faz parte do histórico de mensagens visível para o cliente.

A resposta contém `outcome` e o `dialog` atualizado. Os resultados são `assigned`, `queued`, `team_unassigned` ou `unassigned`. Verifique o resultado real em vez de presumir que um atendente humano aceitou a conversa.

Uma transferência mantém o mesmo `dialogId`. Assim que seu agente perder a propriedade, pare de responder ou de tentar fechar essa caixa de diálogo.

### Fechar uma caixa de diálogo resolvida

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

A resposta contém `dialog` com `status=closed` e `canAct=false`. Seu agente não pode continuar enviando mensagens ou transferindo essa caixa de diálogo fechada. Uma mensagem de cliente válida subsequente inicia uma nova caixa de diálogo e executa as regras de atribuição do número novamente.

## Tratar erros e novas tentativas

As respostas de erro usam um objeto `error` com `status`, `code`, `message` e `requestId`. Guarde o ID da solicitação ao entrar em contato com o suporte da YCloud.

| Status HTTP / código de exemplo | Ação |
| - | - |
| `400 INVALID_ARGUMENT` | Corrija os campos da solicitação, incluindo um destino de transferência ausente. |
| `401` ou `403 CREDENTIAL_TYPE_NOT_SUPPORTED` / `BOT_MISMATCH` | Verifique a chave de API da conta, o tipo de credencial, o Bot ID e a propriedade do locatário. |
| `404 NOT_FOUND` | Verifique os IDs e o escopo do locatário. Se um destino de transferência não estiver mais disponível, liste os destinos novamente. |
| `409 NOT_CURRENT_HANDLER` / `DIALOG_CLOSED` | Interrompa o trabalho desatualizado. Verifique novamente o diálogo atual em vez de tentar a mesma ação repetidamente. |
| `429`, `503` ou um tempo limite de rede | Reduza o ritmo (backoff) nas leituras. Para gravações, inspecione o estado, o histórico e as confirmações antes de tentar novamente, pois a solicitação original pode ter sido bem-sucedida. |

<Warning>
  `sourceEventId` e `X-Request-Id` oferecem suporte ao rastreamento; nenhum deles é uma chave de idempotência. A API não oferece desduplicação entre solicitações. Uma nova tentativa cega após um resultado incerto pode enviar uma mensagem duplicada.
</Warning>

Monitore **Exibir logs de entrega** para falhas de Webhook. Mantenha IDs de eventos, IDs de diálogos, IDs de mensagens, IDs de solicitações e resultados de processamento. As novas tentativas de Webhook são limitadas; portanto, use consultas de diálogo e histórico para recuperar o contexto quando necessário.

## Perguntas frequentes

### Posso conectar um agente criado com meu próprio framework de IA?

Sim. Seu serviço deve receber e verificar os webhooks da YCloud e chamar a API REST do Inbox. Você controla como seu serviço gera respostas, acessa o conhecimento e chama suas próprias ferramentas.

### Meu agente receberá todas as conversas do Inbox?

Não. Seu endpoint dedicado recebe eventos dentro do seu escopo de tratamento, juntamente com notificações relevantes de atribuição e encerramento e confirmações de suas próprias mensagens enviadas. Novas mensagens após uma transferência para um atendente humano não são encaminhadas para o agente anterior. Eventos gerados anteriormente ainda podem chegar com atraso.

### Meu agente pode usar mensagens de diálogos anteriores?

Sim. O endpoint de histórico retorna o histórico visível ao cliente para a conversa, incluindo diálogos anteriores. Use o `dialogId` da própria mensagem quando disponível; não presuma que todas as mensagens retornadas pertencem à sessão de atendimento atual.

### O que acontece se eu alterar o agente atribuído a um número?

Os diálogos abertos atualmente gerenciados por um bot são reatribuídos de forma assíncrona de acordo com o comportamento de roteamento do número. O agente que está saindo recebe `assignmentRole=outgoing` e deve interromper seu trabalho. O novo agente recebe `reason=phone_binding_changed` com `assignmentRole=incoming`; ele deve consultar o diálogo atual e o histórico antes de continuar.

O ID do diálogo e o histórico permanecem inalterados. Os diálogos atendidos atualmente por um humano não são transferidos para o novo bot por meio dessa reatribuição de número. Depois que outro bot o substitui, o bot antigo não pode mais consultar esse diálogo por meio desses endpoints.

### A resposta do meu webhook se torna a resposta para o cliente?

Não. Retorne `2xx` para confirmar o recebimento do evento e, em seguida, envie a resposta ao cliente por meio de `POST /v2/inbox/dialogs/{dialogId}/messages`.

### Por que meu agente recebe `actionable=true` para sua própria resposta?

O campo descreve quem estava gerenciando o diálogo aberto quando o evento foi gerado. Eventos de criação de saída e confirmações de entrega podem, portanto, ter `actionable=true`. Acione uma resposta normal de IA apenas para um evento `inbox.message.created` de entrada e considere as alterações no responsável pelo diálogo em tempo real.


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