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

# Formato de respuesta

> Payload que Jelou envía a tu webhookUrl con las respuestas del agente

## Descripción

Cuando el agente genera una burbuja, Jelou hace `POST` a tu `webhookUrl` con el mensaje formateado. La entrega tiene un **límite total de 20 segundos** que cubre hasta **3 intentos** — ver [Entrega y reintentos](#entrega-y-reintentos).

## Envelope

```json theme={null}
{
  "event": "message",
  "botId": "BOT_ID",
  "userId": "user-123",
  "message": {
    "messageId": "msg-1",
    "type": "TEXT"
  }
}
```

| Campo     | Descripción                                                                  |
| --------- | ---------------------------------------------------------------------------- |
| `event`   | Siempre `"message"`.                                                         |
| `botId`   | Identificador del agente.                                                    |
| `userId`  | El `referenceId` que enviaste en la interacción entrante.                    |
| `message` | Objeto según el tipo de burbuja (tabla abajo). Incluye `messageId` y `type`. |

## Headers de la entrega

* `Content-Type: application/json`
* Auth según `credentials.auth` (si está configurada):
  * `api_key` → header configurable (default `X-Api-Key`)
  * `bearer` → `Authorization: Bearer <value>`
  * `basic` → `value` es la cadena completa `username:password` antes de Base64; el header es `Authorization: Basic <base64(username:password)>`
* `X-Jelou-Signature: sha256=<hmac>` calculado sobre los bytes exactos del body HTTP crudo (siempre presente; la signing key es obligatoria). Verifica con ese raw body antes de parsearlo como JSON; no vuelvas a serializar el objeto parseado.

## Tipos de mensaje (`message`)

| Tipo saliente       | Origen típico                          | Campos                                                                      |
| ------------------- | -------------------------------------- | --------------------------------------------------------------------------- |
| `TEXT`              | Burbuja de texto sin opciones          | `messageId`, `type`, `text`                                                 |
| `BUTTONS`           | Texto con opciones, o burbuja `BUTTON` | `messageId`, `type`, `text`, `title`, `options[]` (`title`, `description`)  |
| `QUICK_REPLY`       | Burbuja `QUICK_REPLY`                  | `messageId`, `type`, `text`, `options[]` (`title`, `description`)           |
| `LIST`              | Burbuja `LIST`                         | `messageId`, `type`, `text`, `button`, `options[]` (`title`, `description`) |
| `IMAGE` / `VIDEO`   | Media                                  | `messageId`, `type`, `mediaUrl`, `caption`                                  |
| `DOCUMENT` / `FILE` | Archivo                                | `messageId`, `type`, `mediaUrl`, `filename`, `caption`                      |
| `AUDIO`             | Audio                                  | `messageId`, `type`, `mediaUrl`                                             |
| `LOCATION`          | Ubicación                              | `messageId`, `type`, `coordinates`, `address`                               |
| `CONTACTS`          | Contactos                              | `messageId`, `type`, `contacts`                                             |

<Note>
  Un texto del flujo con opciones interactivas suele llegar como `type: "BUTTONS"`, no como `TEXT`. El tipo en el wire de respuestas rápidas es `QUICK_REPLY` (con guion bajo). El bloque **Sticker** no está disponible en el builder para este canal.
</Note>

## Ejemplos

<Tabs>
  <Tab title="TEXT">
    ```json theme={null}
    {
      "event": "message",
      "botId": "BOT_ID",
      "userId": "user-123",
      "message": {
        "messageId": "msg-1",
        "type": "TEXT",
        "text": "¿En qué podemos ayudarte?"
      }
    }
    ```
  </Tab>

  <Tab title="BUTTONS">
    ```json theme={null}
    {
      "event": "message",
      "botId": "BOT_ID",
      "userId": "user-123",
      "message": {
        "messageId": "msg-2",
        "type": "BUTTONS",
        "text": "Elige una opción",
        "title": "Menú",
        "options": [
          { "title": "Soporte", "description": "" },
          { "title": "Ventas", "description": "" }
        ]
      }
    }
    ```
  </Tab>

  <Tab title="IMAGE">
    ```json theme={null}
    {
      "event": "message",
      "botId": "BOT_ID",
      "userId": "user-123",
      "message": {
        "messageId": "msg-3",
        "type": "IMAGE",
        "mediaUrl": "https://cdn.example.com/foto.jpg",
        "caption": "Comprobante"
      }
    }
    ```
  </Tab>
</Tabs>

## Entrega y reintentos

Tu endpoint debe responder 2xx. Jelou reintenta una entrega fallida hasta **3 intentos en total**, todos dentro de un único **límite total de 20 segundos** — ese límite incluye cada intento y las esperas entre ellos, así que la entrega de un mensaje nunca tarda más de 20 segundos.

| Tu respuesta                              | ¿Se reintenta?                                                                                                                                            |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `5xx`                                     | Sí                                                                                                                                                        |
| `429`                                     | Sí, esperando exactamente el `Retry-After` que pidas. Si esa espera no cabe en el tiempo que queda, la entrega falla en vez de reintentar antes de tiempo |
| Conexión rechazada, socket reseteado      | Sí                                                                                                                                                        |
| Fallo de DNS (el hostname no resuelve)    | No — un `webhookUrl` que no resuelve es un error de configuración, no un blip                                                                             |
| `4xx` distinto de `429`                   | No — un request idéntico no puede cambiar la respuesta                                                                                                    |
| Sin respuesta dentro del límite (timeout) | No                                                                                                                                                        |
| `2xx`                                     | Entregado                                                                                                                                                 |

El backoff entre intentos es exponencial con jitter (aproximadamente 300 ms y luego 600 ms).

<Warning>
  **Tienes que deduplicar.** Cada intento del mismo mensaje lleva un body idéntico, un `message.messageId` idéntico y una `X-Jelou-Signature` idéntica. Trata `message.messageId` como clave de idempotencia: si ya lo procesaste, responde 2xx y no hagas nada más. Responder `5xx` después de haber procesado un mensaje hará que Jelou lo entregue de nuevo.
</Warning>

Cada entrega lleva un header `X-Jelou-Delivery-Attempt` (`1`, `2`, `3`) que te dice de qué intento se trata — útil para logs.

<Warning>
  Ese header **no** está autenticado: el HMAC cubre el body, así que quien replique un request capturado puede ponerle el valor que quiera. No lo uses como protección anti-replay. Verifica primero `X-Jelou-Signature` y después deduplica por el `message.messageId` autenticado.
</Warning>

Si todos los intentos fallan, el mensaje no se entrega y el turno se aborta; en esta versión no hay reentrega encolada.
