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

> Conecta tu propio agente de IA a YCloud Inbox para recibir mensajes de clientes, enviar respuestas y transferir conversaciones a tu equipo.

<Note>
  External Agent está disponible en los **planes YCloud Pro y superiores**.
</Note>

Conecta tu propio agente de IA a YCloud como un **External Agent**. Tu agente se ejecuta en tu infraestructura y colabora junto con tus agentes humanos en Inbox: recibe mensajes de clientes, envía respuestas, recupera el historial de conversaciones y transfiere conversaciones a tu equipo.

YCloud envía eventos a tu URL de webhook. Tu servicio procesa esos eventos y llama a la API REST de Inbox para responder o gestionar la conversación. YCloud no llama a tu API de inferencia de IA, y la respuesta HTTP de tu webhook no se envía al cliente.

## Antes de comenzar

Prepara:

* Un endpoint HTTPS accesible públicamente que acepte solicitudes JSON `POST`.
* Un servicio que verifique las firmas de webhook, almacene eventos, evite el procesamiento de duplicados y ejecute tu agente de IA.
* Una clave de API estándar de tu cuenta de YCloud, almacenada en tu servidor. Las claves de API de Developer App no son compatibles con estos endpoints de Inbox.
* Un número comercial de WhatsApp conectado a YCloud y un equipo o agente humano para recibir transferencias.

Usa `https://api.ycloud.com` como la URL base de la API.

## Conecta tu agente

### 1. Crear un External Agent

1. En la consola de YCloud, abre **AI Agent** > **AI Agent**.
2. Selecciona **Create Agent** > **External Agent**.
3. Introduce un **Name** y tu **Webhook URL**, como `https://agent.example.com/webhooks/ycloud`.
4. Selecciona **Save**.
5. Copia el **Bot ID** generado desde los detalles del agente. Usa este valor como `botId` en las solicitudes de la API; no necesitas proporcionar un ID de tu propio 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="Formulario Create External Agent de YCloud con los controles Name, Webhook URL y Save." width="3024" height="1656" data-path="product-assets/external-agent-2026-10-20/create-external-agent.png" />
</Frame>

La página de detalles muestra el endpoint de webhook, su **Signing secret** y los cuatro tipos de eventos. Usa el signing secret para verificar los eventos entrantes. Selecciona **View delivery logs** para investigar la entrega de webhooks. El mismo endpoint también está disponible en la página **Webhooks** .

Mantén el signing secret del endpoint separado de tu clave de API. El secreto verifica los webhooks entrantes; `X-API-Key` autentica tus solicitudes REST salientes. Un Bot ID es un identificador, no una credencial.

### 2. Asignar un número de WhatsApp

Abre **WhatsApp Manager** > **WhatsApp accounts**, luego abre la configuración del número comercial y selecciona **Inbox** > **Assignment**. En **Priority 3 Assign to**, selecciona **AI Agent**, y después elige tu agente en la sección **External Agent** del selector de agentes.

Comprueba la prioridad de las reglas: la asignación al propietario del cliente o al último agente que atendió puede tener precedencia. Consulta [Conversation assignment rules](/es/documentation/channels/whatsapp-accounts-management/phone-number-management/conversation-assignment-rules).

Un solo External Agent puede gestionar múltiples números. Cada número está asociado con un solo bot a la vez. Administra estas asociaciones en la configuración de asignación del número.

### 3. Procesar una conversación

1. Un cliente envía un mensaje de WhatsApp a tu número comercial.
2. YCloud abre o continúa un diálogo de Inbox y aplica las reglas de asignación del número.
3. Cuando tu External Agent gestiona el diálogo, YCloud envía los eventos de webhook correspondientes a su endpoint.
4. Tu servicio verifica y almacena el evento, devuelve `2xx` y procesa la entrada del cliente de forma asíncrona.
5. Tu servicio llama a la API de Inbox para responder, transferir o cerrar el diálogo.
6. Tu servicio rastrea la entrega mediante `whatsapp.message.updated`.

## Comprender los identificadores

| Identificador | Propósito |
| - | - |
| `botId` | El ID del External Agent generado por YCloud. |
| `phoneNumberId` | El ID del número comercial de WhatsApp de YCloud. |
| `conversationId` | La relación continua entre un cliente y un número comercial. |
| `dialogId` | Una sesión de servicio, desde su apertura hasta su cierre. Una transferencia mantiene este ID; un mensaje entrante nuevo y válido después del cierre inicia un nuevo diálogo. |
| Webhook `id` | Un ID de evento opaco utilizado para la deduplicación. No codifica el orden de los eventos. |
| `inboxMessage.id` / `inboxMessageId` | El ID del mensaje de Inbox, utilizado para referencias de respuestas y correlación de entrega. |
| `channelMessageId` / `wamid` | Identificadores de mensajes de canal y de WhatsApp. Almacénalos por separado de los ID de Inbox. |

Los ID son cadenas. Las marcas de tiempo utilizan UTC RFC 3339. Usa `dialogId`, no `conversationId`, en las rutas de la API del diálogo.

## Recibir eventos de webhook

Los cuatro tipos de eventos llegan a la **misma URL de webhook**. Cada entrega dedicada de External Agent incluye `id`, `type`, `apiVersion`, `createTime` e `externalBotContext`, además del objeto de negocio correspondiente.

| Evento | Objeto de negocio | Qué debe hacer tu servicio |
| - | - | - |
| `inbox.message.created` | `inboxMessage` | Procesa la entrada del cliente solo cuando `direction=inbound` y `actionable=true`. Sincroniza los mensajes salientes sin activar otra respuesta de IA. |
| `inbox.conversation.assigned` | `inboxConversation` | Rastrea los cambios de asignación. Detén el procesamiento cuando tu agente transfiera la conversación; recupera el historial cuando asuma un diálogo existente. |
| `inbox.conversation.status_updated` | `inboxConversation` | Rastrea el estado del diálogo y detén el trabajo pendiente cuando se cierre. |
| `whatsapp.message.updated` | `whatsappMessage` | Rastrea el estado de entrega o el fallo de un mensaje enviado por tu agente. |

### Verificar, almacenar y confirmar

Verifica el encabezado `YCloud-Signature` usando el secreto de firma del endpoint y el **cuerpo sin procesar de la solicitud HTTP**, antes de analizar el JSON. El formato de la firma es:

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

Usa una comparación de tiempo constante y valida la marca de tiempo con una tolerancia razonable, como cinco minutos. Consulta [Implementar un receptor de webhook](/es/api-reference/guides/api-fundamentals/implement-a-webhook-receiver) para ver ejemplos de verificación de firma.

Persiste el evento y su estado de deduplicación, y luego devuelve de inmediato cualquier respuesta `2xx` exitosa. Ejecuta la inferencia de IA y otros procesos lentos de forma asíncrona. Las respuestas que no sean `2xx` y los tiempos de espera pueden activar un número limitado de reintentos.

Deduplica por **`(botId, event.id)`**, dentro de tu cuenta de YCloud. Un cambio de asignación puede notificar tanto al agente saliente como al entrante; usar únicamente un ID de evento global puede descartar la notificación de uno de los agentes. Mantén el estado de procesamiento para que un evento repetido no envíe una segunda respuesta.

### Ejemplo: mensaje del 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" } }
  }
}
```

Lee el contenido del cliente desde `inboxMessage.inbound`. Los eventos de creación salientes usan `inboxMessage.outbound` en su lugar. Conserva los tipos de mensajes y campos desconocidos por motivos de compatibilidad en lugar de tratarlos como texto.

### Decidir si responder

`externalBotContext` es una instantánea tomada cuando se genera la notificación. Su campo `botId` identifica al bot receptor; `assignee` identifica al gestor real en esa instantánea. Estos pueden diferir tras una transferencia.

`actionable=true` significa que el bot receptor estaba gestionando un diálogo abierto en ese momento. También puede ser `true` en un evento de creación saliente o en un recibo de entrega.

<Warning>
  No actives una respuesta de IA únicamente a partir de `actionable`. Comprueba el tipo de evento y la dirección del mensaje. Un evento retrasado no demuestra que tu agente aún gestione el diálogo. Consulta el diálogo actual cuando sea necesario y gestiona los errores de propiedad de cada solicitud de escritura.
</Warning>

Los eventos pueden llegar más de una vez o desordenados. No existe un campo de secuencia de eventos. Agrupa el trabajo por `dialogId` y evita procesar dos veces la misma entrada del cliente. Un evento de asignación inicial no debe activar de forma independiente una segunda respuesta al mensaje inicial del cliente.

### ¿Qué ocurre tras una transferencia o cierre?

Los nuevos mensajes de clientes y las respuestas humanas ya no se entregan al External Agent anterior una vez que el diálogo es gestionado por un humano, queda en cola, sin asignar o cerrado.

El agente anterior sigue recibiendo la notificación correspondiente de asignación saliente o de cierre. Las notificaciones de creación y los recibos de entrega de los mensajes que ya envió permanecen asociados a ese agente emisor, incluso después de una transferencia o reasignación de número.

Las notificaciones generadas previamente pueden llegar tarde. Cancela el trabajo pendiente cuando cambie la asignación o se cierre el diálogo, y no lo reanudes únicamente porque un evento antiguo tenga `actionable=true`.

Los endpoints de webhook normales conservan su comportamiento de suscripción y su formato de carga útil existentes. La entrega dedicada de External Agent añade `externalBotContext`; no exijas ese campo en entregas ordinarias de `whatsapp.message.updated`.

## Usar la API de Inbox

Envía `X-API-Key` en cada solicitud y `Content-Type: application/json` en solicitudes `POST`. Todos los ejemplos que aparecen a continuación usan identificadores de marcador de posición. Sustitúyelos por valores de tu agente y de los eventos recibidos.

| Operación | Método y ruta |
| - | - |
| Obtener el diálogo actual | `GET /v2/inbox/dialogs/{dialogId}` |
| Obtener el historial de la conversación | `GET /v2/inbox/dialogs/{dialogId}/messages` |
| Listar destinos de transferencia | `GET /v2/inbox/handoffTargets` |
| Enviar una respuesta | `POST /v2/inbox/dialogs/{dialogId}/messages` |
| Transferir | `POST /v2/inbox/dialogs/{dialogId}/handoff` |
| Cerrar el diálogo | `POST /v2/inbox/dialogs/{dialogId}/close` |

Las respuestas correctas devuelven directamente el objeto de negocio, sin un contenedor `data`. Las operaciones de escritura comprueban que el diálogo esté abierto y gestionado actualmente por el bot especificado. YCloud determina el remitente y el cliente a partir del diálogo; no envíes `tenantId`, `from`, `to`, `recipient`, `operatorId` ni `source` para anularlos.

### Obtener el diálogo actual

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

Comprueba `status`, `assignee` y `canAct`. El campo `bot` puede hacer referencia al último bot externo asociado y no es una prueba de la propiedad actual. `canAct` refleja los permisos del bot consultado en el momento de la solicitud; una escritura posterior seguirá realizando sus propias comprobaciones.

### Recuperar el historial de conversaciones

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

La respuesta contiene `items` y `nextCursor`. Cada elemento incluye un mensaje de Inbox `id`, `direction`, `type`, `content` y `createTime`. Para un mensaje de texto, lee `content.text.body`.

El historial incluye los mensajes visibles para el cliente de la **misma conversación**, posiblemente a través de diálogos anteriores. El propio `dialogId` de un mensaje puede ser diferente del diálogo solicitado o no estar presente en un historial más antiguo. Las notas internas, los resúmenes privados de derivación y los mensajes internos del sistema quedan excluidos.

Los mensajes se devuelven en orden ascendente por ID de mensaje, de más antiguos a más recientes. Omite `after` en la primera solicitud; pasa `nextCursor` sin modificar como `after` en las solicitudes posteriores hasta que sea `null`. El valor predeterminado de `limit` es 50, con un rango de 1 a 100. La lista de destinatarios de derivación utiliza el mismo patrón de paginación.

### Enviar una respuesta

```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` y `message` son obligatorios. `sourceEventId` y `replyToMessageId` son opcionales. La referencia de respuesta es un **ID de mensaje de Inbox**, como `inboxMessage.id`; no es un `wamid`.

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

Comprueba `status` incluso cuando HTTP devuelva `200`. `accepted` significa que el mensaje entró en la canalización de envío, no que haya llegado al cliente. Una respuesta `200` también puede indicar `failed`; es posible que `channelMessageId` aún no esté disponible.

Usa `whatsapp.message.updated` para rastrear los estados posteriores, incluidos `sent`, `delivered`, `read`, `failed`, `expired` o `deleted`. Correlaciona el `externalBotContext.inboxMessageId` del recibo con el `inboxMessageId` de la respuesta de envío. Revisa `whatsappMessage.errorCode` y `errorMessage` en busca de errores.

#### Tipos de mensaje

El objeto `message` utiliza el modelo estándar de mensajes de WhatsApp: `type` más el campo de contenido correspondiente. Entre los ejemplos se incluyen `text`, `image`, `video`, `audio`, `document`, `sticker`, `location`, `contacts`, `interactive`, `template` y `reaction`. Los requisitos existentes de envío y plantillas de WhatsApp siguen aplicando.

Por ejemplo, sustituye el 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 archivos multimedia, proporciona `id` o bien `link`. Para conocer las estructuras del cuerpo del mensaje, consulta [Enviar un mensaje de WhatsApp](/es/api-reference/guides/whatsapp-platform/send-whatsapp-message). Envía la respuesta a través del endpoint de Inbox anterior para que utilice las comprobaciones de gestión del diálogo y el contexto de Inbox. No envíes ni dependas de `filterUnsubscribed` o `filterBlocked` en esta solicitud de mensaje de Inbox.

Las respuestas de los clientes pueden incluir `interactive.button_reply`, `interactive.list_reply`, `interactive.nfm_reply` o `button`. El `response_json` de una respuesta de Flow es una cadena JSON. El tipo entrante `button` no es un tipo de mensaje saliente para este endpoint.

### Derivar a un agente humano

Primero, lista los equipos o agentes disponibles para el número de negocio del 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"
```

Usa `type=agent` para listar los miembros en su lugar. Cada destinatario devuelto proporciona el `id` y el `type` para la solicitud de derivación. El campo opcional `online` de un agente describe la presencia; no garantiza una asignación inmediata.

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

Debes especificar un `target` con `type` (`team` o `agent`) y `id`. No hay una alternativa humana predeterminada. El campo opcional `reason` admite hasta 256 caracteres; el campo opcional `summary` admite hasta 4000 caracteres y no forma parte del historial de mensajes visible para el cliente.

La respuesta contiene `outcome` y el `dialog` actualizado. Los resultados son `assigned`, `queued`, `team_unassigned` o `unassigned`. Comprueba el resultado real en lugar de asumir que un agente humano ha aceptado la conversación.

Una derivación mantiene el mismo `dialogId`. Una vez que tu agente pierda la asignación, deja de responder o de intentar cerrar ese diálogo.

### Cerrar un diálogo resuelto

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

La respuesta contiene `dialog` con `status=closed` y `canAct=false`. Tu agente no puede continuar enviando mensajes ni derivando ese diálogo cerrado. Un mensaje válido posterior del cliente iniciará un nuevo diálogo y volverá a ejecutar las reglas de asignación del número.

## Gestionar errores y reintentos

Las respuestas de error utilizan un objeto `error` con `status`, `code`, `message` y `requestId`. Conserva el ID de solicitud al ponerte en contacto con el soporte de YCloud.

| Código de estado / ejemplo HTTP | Acción |
| - | - |
| `400 INVALID_ARGUMENT` | Corrige los campos de la solicitud, incluido el destinatario de derivación faltante. |
| `401` o `403 CREDENTIAL_TYPE_NOT_SUPPORTED` / `BOT_MISMATCH` | Comprueba la clave de API de la cuenta, el tipo de credencial, el Bot ID y la titularidad del inquilino. |
| `404 NOT_FOUND` | Comprueba los identificadores y el alcance del inquilino. Si un destino de transferencia ya no está disponible, vuelve a listar los destinos. |
| `409 NOT_CURRENT_HANDLER` / `DIALOG_CLOSED` | Detén el trabajo obsoleto. Vuelve a consultar el diálogo actual en lugar de reintentar la misma acción. |
| `429`, `503` o un tiempo de espera de red agotado | Aplica un retroceso exponencial en lecturas. En escrituras, inspecciona el estado, el historial y los recibos antes de reintentar, ya que la solicitud original podría haber tenido éxito. |

<Warning>
  `sourceEventId` y `X-Request-Id` admiten el rastreo; ninguno de los dos es una clave de idempotencia. La API no ofrece deduplicación entre solicitudes. Un reintento a ciegas tras un resultado incierto puede enviar un mensaje duplicado.
</Warning>

Monitorea **View delivery logs** para detectar fallos de webhook. Conserva los ID de evento, ID de diálogo, ID de mensaje, ID de solicitud y los resultados del procesamiento. Los reintentos de Webhook son limitados, así que usa consultas de diálogo e historial para recuperar el contexto cuando sea necesario.

## Preguntas frecuentes

### ¿Puedo conectar un agente creado con mi propio framework de IA?

Sí. Tu servicio debe recibir y verificar los webhooks de YCloud y llamar a la API REST de Inbox. Tú controlas cómo genera respuestas tu servicio, cómo accede al conocimiento y cómo invoca sus propias herramientas.

### ¿Mi agente recibirá todas las conversaciones de Inbox?

No. Su endpoint dedicado recibe eventos dentro de su ámbito de gestión, junto con notificaciones pertinentes de asignación y cierre, y recibos de sus propios mensajes enviados. Los nuevos mensajes tras una transferencia a un agente humano no se reenvían al agente anterior. Los eventos generados previamente aún pueden llegar con retraso.

### ¿Puede mi agente utilizar mensajes de diálogos anteriores?

Sí. El endpoint de historial devuelve el historial visible para el cliente correspondiente a la conversación, incluidos los diálogos anteriores. Utiliza el propio `dialogId` de cada mensaje cuando esté disponible; no asumas que cada mensaje devuelto pertenece a la sesión de servicio actual.

### ¿Qué ocurre si cambio el agente asignado a un número?

Los diálogos abiertos gestionados actualmente por un bot se reasignan de forma asíncrona según el comportamiento de enrutamiento del número. El agente saliente recibe `assignmentRole=outgoing` y debe detener su trabajo. El agente entrante recibe `reason=phone_binding_changed` con `assignmentRole=incoming`; debe consultar el diálogo actual y el historial antes de continuar.

El ID del diálogo y el historial permanecen sin cambios. Los diálogos gestionados actualmente por una persona no se transfieren al nuevo bot mediante esta reasignación de número. Después de que otro bot lo sustituya, el bot anterior ya no podrá consultar ese diálogo a través de estos endpoints.

### ¿La respuesta de mi webhook se convierte en la respuesta al cliente?

No. Devuelve `2xx` para confirmar la recepción del evento y luego envía la respuesta al cliente a través de `POST /v2/inbox/dialogs/{dialogId}/messages`.

### ¿Por qué recibe mi agente `actionable=true` para su propia respuesta?

El campo describe quién gestionaba el diálogo abierto cuando se generó el evento. Por tanto, los eventos de creación de salida y los recibos de entrega pueden tener `actionable=true`. Activa una respuesta de IA normal únicamente para un evento `inbox.message.created` entrante, y ten en cuenta los cambios en la titularidad del diálogo en vivo.


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