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

# Mensajes Interactivos

> Envía botones, listas y flows interactivos por WhatsApp

Los mensajes interactivos permiten a los usuarios responder con un tap en lugar de escribir. Whaapy soporta en esta fase:

* **Botones**: Hasta 3 opciones rápidas
* **Listas**: Menús desplegables con secciones
* **CTA URL**: Botón que abre una URL en el navegador
* **Flows**: Apertura de un Flow de WhatsApp ya creado en Meta

<Note>
  Los mensajes interactivos solo pueden enviarse dentro de la ventana de 24 horas. Fuera de la ventana, usa [templates](/api-reference/messages/send-templates).
</Note>

***

## Botones (Reply Buttons)

Muestra hasta **3 botones** con opciones rápidas. Ideal para preguntas simples o menús pequeños.

### Ejemplo Básico

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.whaapy.com/messages/v1 \
    -H "Authorization: Bearer wha_TU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+5215512345678",
      "type": "interactive",
      "interactive": {
        "type": "button",
        "body": {
          "text": "¿Qué te gustaría hacer hoy?"
        },
        "action": {
          "buttons": [
            {
              "type": "reply",
              "reply": {
                "id": "ver_menu",
                "title": "Ver Menú"
              }
            },
            {
              "type": "reply",
              "reply": {
                "id": "hacer_pedido",
                "title": "Hacer Pedido"
              }
            },
            {
              "type": "reply",
              "reply": {
                "id": "hablar_agente",
                "title": "Hablar con Agente"
              }
            }
          ]
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.whaapy.com/messages/v1', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer wha_TU_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      to: '+5215512345678',
      type: 'interactive',
      interactive: {
        type: 'button',
        body: {
          text: '¿Qué te gustaría hacer hoy?'
        },
        action: {
          buttons: [
            {
              type: 'reply',
              reply: { id: 'ver_menu', title: 'Ver Menú' }
            },
            {
              type: 'reply',
              reply: { id: 'hacer_pedido', title: 'Hacer Pedido' }
            },
            {
              type: 'reply',
              reply: { id: 'hablar_agente', title: 'Hablar con Agente' }
            }
          ]
        }
      }
    })
  });
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.whaapy.com/messages/v1',
      headers={
          'Authorization': 'Bearer wha_TU_API_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'to': '+5215512345678',
          'type': 'interactive',
          'interactive': {
              'type': 'button',
              'body': {
                  'text': '¿Qué te gustaría hacer hoy?'
              },
              'action': {
                  'buttons': [
                      {'type': 'reply', 'reply': {'id': 'ver_menu', 'title': 'Ver Menú'}},
                      {'type': 'reply', 'reply': {'id': 'hacer_pedido', 'title': 'Hacer Pedido'}},
                      {'type': 'reply', 'reply': {'id': 'hablar_agente', 'title': 'Hablar con Agente'}}
                  ]
              }
          }
      }
  )
  ```
</RequestExample>

### Con Header y Footer

<RequestExample>
  ```json Request theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "button",
      "header": {
        "type": "text",
        "text": "🍕 Pizzería Whaapy"
      },
      "body": {
        "text": "¡Bienvenido! ¿En qué podemos ayudarte?"
      },
      "footer": {
        "text": "Responde con un botón"
      },
      "action": {
        "buttons": [
          {
            "type": "reply",
            "reply": { "id": "menu", "title": "Ver Menú" }
          },
          {
            "type": "reply",
            "reply": { "id": "pedido", "title": "Mi Pedido" }
          }
        ]
      }
    }
  }
  ```
</RequestExample>

### Con Header de Imagen

<RequestExample>
  ```json Request theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "button",
      "header": {
        "type": "image",
        "image": {
          "link": "https://example.com/promo.jpg"
        }
      },
      "body": {
        "text": "¡Promoción especial! 2x1 en pizzas familiares."
      },
      "action": {
        "buttons": [
          {
            "type": "reply",
            "reply": { "id": "aprovechar", "title": "¡Quiero!" }
          },
          {
            "type": "reply",
            "reply": { "id": "mas_info", "title": "Más Info" }
          }
        ]
      }
    }
  }
  ```
</RequestExample>

### Campos de Botones

<ParamField body="interactive.type" type="string" required>
  Debe ser `"button"` para reply buttons.
</ParamField>

<ParamField body="interactive.header" type="object">
  Header opcional. Puede ser `text`, `image`, `video`, o `document`.
</ParamField>

<ParamField body="interactive.body.text" type="string" required>
  Texto principal del mensaje. Máximo 1024 caracteres.
</ParamField>

<ParamField body="interactive.footer.text" type="string">
  Texto secundario en gris. Máximo 60 caracteres.
</ParamField>

<ParamField body="interactive.action.buttons" type="array" required>
  Array de 1-3 botones. Cada botón tiene `type: "reply"` y objeto `reply` con `id` y `title`.
</ParamField>

<ParamField body="button.reply.id" type="string" required>
  ID único del botón. Lo recibirás en el webhook cuando el usuario responda. Máximo 256 caracteres.
</ParamField>

<ParamField body="button.reply.title" type="string" required>
  Texto visible del botón. Máximo 20 caracteres.
</ParamField>

***

## Listas (List Messages)

Menús desplegables con hasta **10 opciones** organizadas en secciones. Ideal para catálogos, horarios, o menús extensos.

### Ejemplo Básico

<RequestExample>
  ```json Request theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "list",
      "body": {
        "text": "Elige una opción de nuestro menú:"
      },
      "action": {
        "button": "Ver Opciones",
        "sections": [
          {
            "title": "Pizzas",
            "rows": [
              {
                "id": "pizza_margarita",
                "title": "Margarita",
                "description": "Tomate, mozzarella, albahaca - $150"
              },
              {
                "id": "pizza_pepperoni",
                "title": "Pepperoni",
                "description": "Pepperoni, mozzarella - $180"
              }
            ]
          },
          {
            "title": "Bebidas",
            "rows": [
              {
                "id": "bebida_refresco",
                "title": "Refresco",
                "description": "Coca-Cola, Sprite, Fanta - $35"
              },
              {
                "id": "bebida_agua",
                "title": "Agua",
                "description": "Natural o mineral - $25"
              }
            ]
          }
        ]
      }
    }
  }
  ```
</RequestExample>

### Con Header y Footer

<RequestExample>
  ```json Request theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "list",
      "header": {
        "type": "text",
        "text": "🍕 Menú Pizzería Whaapy"
      },
      "body": {
        "text": "Tenemos las mejores pizzas de la ciudad. ¡Elige tu favorita!"
      },
      "footer": {
        "text": "Envío gratis en pedidos mayores a $300"
      },
      "action": {
        "button": "Ver Menú",
        "sections": [
          {
            "title": "Pizzas Clásicas",
            "rows": [
              {
                "id": "margarita",
                "title": "Margarita",
                "description": "Tomate, mozzarella, albahaca fresca"
              },
              {
                "id": "pepperoni",
                "title": "Pepperoni",
                "description": "Pepperoni premium, mozzarella"
              },
              {
                "id": "hawaiana",
                "title": "Hawaiana",
                "description": "Jamón, piña, mozzarella"
              }
            ]
          },
          {
            "title": "Pizzas Especiales",
            "rows": [
              {
                "id": "4_quesos",
                "title": "4 Quesos",
                "description": "Mozzarella, parmesano, gorgonzola, provolone"
              },
              {
                "id": "bbq_chicken",
                "title": "BBQ Chicken",
                "description": "Pollo, salsa BBQ, cebolla caramelizada"
              }
            ]
          }
        ]
      }
    }
  }
  ```
</RequestExample>

### Campos de Listas

<ParamField body="interactive.type" type="string" required>
  Debe ser `"list"` para list messages.
</ParamField>

<ParamField body="interactive.header" type="object">
  Header opcional. Solo soporta `type: "text"` en listas.
</ParamField>

<ParamField body="interactive.body.text" type="string" required>
  Texto principal. Máximo 1024 caracteres.
</ParamField>

<ParamField body="interactive.footer.text" type="string">
  Texto secundario. Máximo 60 caracteres.
</ParamField>

<ParamField body="interactive.action.button" type="string" required>
  Texto del botón que abre la lista. Máximo 20 caracteres.
</ParamField>

<ParamField body="interactive.action.sections" type="array" required>
  Array de secciones. Máximo 10 secciones, máximo 10 rows en total.
</ParamField>

### Campos de Section

<ParamField body="section.title" type="string">
  Título de la sección. Máximo 24 caracteres. Requerido si hay más de una sección.
</ParamField>

<ParamField body="section.rows" type="array" required>
  Array de opciones dentro de la sección.
</ParamField>

### Campos de Row

<ParamField body="row.id" type="string" required>
  ID único de la opción. Lo recibirás en el webhook. Máximo 200 caracteres.
</ParamField>

<ParamField body="row.title" type="string" required>
  Título visible de la opción. Máximo 24 caracteres.
</ParamField>

<ParamField body="row.description" type="string">
  Descripción adicional. Máximo 72 caracteres.
</ParamField>

***

## CTA URL (Call-to-Action URL)

Muestra un botón que abre una **URL en el navegador** del usuario al hacer tap. Ideal para dirigir a páginas de pago, formularios web, catálogos online o cualquier enlace externo.

### Ejemplo Básico

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.whaapy.com/messages/v1 \
    -H "Authorization: Bearer wha_TU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+5215512345678",
      "type": "interactive",
      "interactive": {
        "type": "cta_url",
        "body": {
          "text": "¡Tu pedido #12345 está listo! Haz seguimiento en tiempo real."
        },
        "action": {
          "name": "cta_url",
          "parameters": {
            "display_text": "Rastrear Pedido",
            "url": "https://mitienda.com/tracking/12345"
          }
        }
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.whaapy.com/messages/v1', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer wha_TU_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      to: '+5215512345678',
      type: 'interactive',
      interactive: {
        type: 'cta_url',
        body: {
          text: '¡Tu pedido #12345 está listo! Haz seguimiento en tiempo real.'
        },
        action: {
          name: 'cta_url',
          parameters: {
            display_text: 'Rastrear Pedido',
            url: 'https://mitienda.com/tracking/12345'
          }
        }
      }
    })
  });
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://api.whaapy.com/messages/v1',
      headers={
          'Authorization': 'Bearer wha_TU_API_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'to': '+5215512345678',
          'type': 'interactive',
          'interactive': {
              'type': 'cta_url',
              'body': {
                  'text': '¡Tu pedido #12345 está listo! Haz seguimiento en tiempo real.'
              },
              'action': {
                  'name': 'cta_url',
                  'parameters': {
                      'display_text': 'Rastrear Pedido',
                      'url': 'https://mitienda.com/tracking/12345'
                  }
              }
          }
      }
  )
  ```
</RequestExample>

### Con Header, Footer e Imagen

<RequestExample>
  ```json Request theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "cta_url",
      "header": {
        "type": "text",
        "text": "🛒 Mi Tienda Online"
      },
      "body": {
        "text": "Aprovecha nuestras ofertas de temporada con hasta 50% de descuento en productos seleccionados."
      },
      "footer": {
        "text": "Oferta válida hasta agotar existencias"
      },
      "action": {
        "name": "cta_url",
        "parameters": {
          "display_text": "Ver Ofertas",
          "url": "https://mitienda.com/ofertas"
        }
      }
    }
  }
  ```
</RequestExample>

<RequestExample>
  ```json Request (con imagen) theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "cta_url",
      "header": {
        "type": "image",
        "image": {
          "link": "https://example.com/promo-banner.jpg"
        }
      },
      "body": {
        "text": "Nueva colección disponible. ¡Sé el primero en verla!"
      },
      "action": {
        "name": "cta_url",
        "parameters": {
          "display_text": "Comprar Ahora",
          "url": "https://mitienda.com/nueva-coleccion"
        }
      }
    }
  }
  ```
</RequestExample>

### Campos de CTA URL

<ParamField body="interactive.type" type="string" required>
  Debe ser `"cta_url"`.
</ParamField>

<ParamField body="interactive.header" type="object">
  Header opcional. Puede ser `text`, `image`, `video`, o `document`.
</ParamField>

<ParamField body="interactive.body.text" type="string" required>
  Texto principal del mensaje. Máximo 1024 caracteres.
</ParamField>

<ParamField body="interactive.footer.text" type="string">
  Texto secundario en gris. Máximo 60 caracteres.
</ParamField>

<ParamField body="interactive.action.name" type="string" required>
  Debe ser `"cta_url"`.
</ParamField>

<ParamField body="interactive.action.parameters.display_text" type="string" required>
  Texto visible del botón. Máximo 30 caracteres.
</ParamField>

<ParamField body="interactive.action.parameters.url" type="string" required>
  URL que se abrirá al hacer tap en el botón. Debe ser una URL válida con `https://`.
</ParamField>

<Note>
  A diferencia de los reply buttons, los CTA URL **no generan un webhook de respuesta** al ser tocados. El usuario simplemente es redirigido al navegador. Si necesitas trackear clics, usa UTM parameters en la URL.
</Note>

***

## Respuesta del Usuario

Cuando el usuario selecciona un botón o item de lista, recibirás un webhook con el `id` seleccionado:

```json Webhook - Botón seleccionado theme={null}
{
  "type": "interactive",
  "interactive": {
    "type": "button_reply",
    "button_reply": {
      "id": "hacer_pedido",
      "title": "Hacer Pedido"
    }
  }
}
```

```json Webhook - Lista seleccionada theme={null}
{
  "type": "interactive",
  "interactive": {
    "type": "list_reply",
    "list_reply": {
      "id": "pizza_pepperoni",
      "title": "Pepperoni",
      "description": "Pepperoni, mozzarella - $180"
    }
  }
}
```

```json Webhook - Flow enviado por el usuario theme={null}
{
  "type": "interactive",
  "interactive": {
    "type": "nfm_reply",
    "nfm_reply": {
      "name": "booking_flow",
      "body": "Sucursal Centro · 2026-03-10 16:00",
      "response_json": "{\"screen\":\"BOOK_APPOINTMENT\",\"values\":{\"branch\":\"Sucursal Centro\",\"slot\":\"2026-03-10 16:00\",\"service\":\"demo\"}}"
    }
  }
}
```

<Tip>
  Usa IDs descriptivos como `"confirmar_pedido_12345"` para facilitar el procesamiento en tu backend.
</Tip>

***

## Respuesta Exitosa

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "messaging_product": "whatsapp",
    "contacts": [
      { "input": "+5215512345678", "wa_id": "5215512345678" }
    ],
    "messages": [
      { "id": "550e8400-e29b-41d4-a716-446655440000", "wamid": "wamid.HBgLNTIxNTUx..." }
    ],
    "data": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "conversationId": "660e8400-e29b-41d4-a716-446655440001",
      "messageType": "interactive",
      "direction": "outbound",
      "status": "sent",
      "createdAt": "2026-01-22T10:30:00Z"
    }
  }
  ```
</ResponseExample>

***

## Límites

| Elemento                              | Límite |
| ------------------------------------- | ------ |
| Botones por mensaje                   | 3      |
| Secciones en lista                    | 10     |
| Rows totales en lista                 | 10     |
| Caracteres en body                    | 1024   |
| Caracteres en título de botón         | 20     |
| Caracteres en display\_text (CTA URL) | 30     |
| Caracteres en título de row           | 24     |
| Caracteres en descripción de row      | 72     |
| Caracteres en footer                  | 60     |

***

## Comparación: Botones vs Listas vs CTA URL

| Aspecto              | Botones             | Listas                     | CTA URL               |
| -------------------- | ------------------- | -------------------------- | --------------------- |
| Opciones máximas     | 3                   | 10                         | 1 (un solo botón)     |
| Visibilidad          | Siempre visibles    | Requiere tap para expandir | Siempre visible       |
| Header con imagen    | Sí                  | No (solo texto)            | Sí                    |
| Descripciones        | No                  | Sí                         | No                    |
| Organización         | Flat                | Secciones                  | Botón único           |
| Webhook al responder | Sí (`button_reply`) | Sí (`list_reply`)          | No (abre navegador)   |
| Mejor para           | Decisiones rápidas  | Catálogos, menús           | Links externos, pagos |

***

## Casos de Uso

<AccordionGroup>
  <Accordion title="Confirmación de pedido">
    ```json theme={null}
    {
      "type": "button",
      "body": { "text": "Tu pedido #12345 está listo. ¿Confirmamos envío?" },
      "action": {
        "buttons": [
          { "type": "reply", "reply": { "id": "confirmar", "title": "Confirmar" } },
          { "type": "reply", "reply": { "id": "modificar", "title": "Modificar" } },
          { "type": "reply", "reply": { "id": "cancelar", "title": "Cancelar" } }
        ]
      }
    }
    ```
  </Accordion>

  <Accordion title="Selección de horario">
    ```json theme={null}
    {
      "type": "list",
      "body": { "text": "Selecciona tu horario de entrega preferido:" },
      "action": {
        "button": "Ver Horarios",
        "sections": [
          {
            "title": "Mañana",
            "rows": [
              { "id": "9am", "title": "9:00 - 11:00 AM" },
              { "id": "11am", "title": "11:00 - 1:00 PM" }
            ]
          },
          {
            "title": "Tarde",
            "rows": [
              { "id": "2pm", "title": "2:00 - 4:00 PM" },
              { "id": "4pm", "title": "4:00 - 6:00 PM" }
            ]
          }
        ]
      }
    }
    ```
  </Accordion>

  <Accordion title="Encuesta de satisfacción">
    ```json theme={null}
    {
      "type": "button",
      "body": { "text": "¿Cómo calificarías tu experiencia con nosotros?" },
      "action": {
        "buttons": [
          { "type": "reply", "reply": { "id": "excelente", "title": "😊 Excelente" } },
          { "type": "reply", "reply": { "id": "buena", "title": "🙂 Buena" } },
          { "type": "reply", "reply": { "id": "mejorar", "title": "😕 Puede mejorar" } }
        ]
      }
    }
    ```
  </Accordion>

  <Accordion title="Link de pago (CTA URL)">
    ```json theme={null}
    {
      "type": "cta_url",
      "body": { "text": "Tu pedido #12345 por $350 MXN está confirmado. Completa el pago en el siguiente enlace:" },
      "footer": { "text": "Link válido por 24 horas" },
      "action": {
        "name": "cta_url",
        "parameters": {
          "display_text": "Pagar Ahora",
          "url": "https://mitienda.com/pago/12345"
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Catálogo web (CTA URL)">
    ```json theme={null}
    {
      "type": "cta_url",
      "header": {
        "type": "image",
        "image": { "link": "https://example.com/catalogo-preview.jpg" }
      },
      "body": { "text": "Explora nuestro catálogo completo con más de 200 productos disponibles." },
      "action": {
        "name": "cta_url",
        "parameters": {
          "display_text": "Ver Catálogo",
          "url": "https://mitienda.com/catalogo"
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Flows de WhatsApp

Usa un Flow ya existente en Meta sin necesidad de crearlo desde Whaapy. El contrato sigue el formato nativo de WhatsApp Cloud API: `type: "interactive"` con `interactive.type: "flow"`.

### Ejemplo Básico

<RequestExample>
  ```json Request theme={null}
  {
    "to": "+5215512345678",
    "type": "interactive",
    "interactive": {
      "type": "flow",
      "header": {
        "type": "text",
        "text": "Reserva tu cita"
      },
      "body": {
        "text": "Completa este flujo para reservar tu cita en menos de 1 minuto."
      },
      "footer": {
        "text": "Datos protegidos"
      },
      "action": {
        "name": "flow",
        "parameters": {
          "flow_message_version": "3",
          "flow_id": "123456789012345",
          "flow_cta": "Abrir flow",
          "flow_token": "lead_987654321",
          "flow_action": "navigate",
          "flow_action_payload": {
            "screen": "BOOK_APPOINTMENT",
            "data": {
              "service": "demo",
              "source": "api"
            }
          }
        }
      }
    }
  }
  ```
</RequestExample>

### Campos de Flow

<ParamField body="interactive.type" type="string" required>
  Debe ser `"flow"`.
</ParamField>

<ParamField body="interactive.action.name" type="string" required>
  Debe ser `"flow"`.
</ParamField>

<ParamField body="interactive.action.parameters.flow_message_version" type="string" required>
  Actualmente debe ser `"3"`.
</ParamField>

<ParamField body="interactive.action.parameters.flow_id" type="string">
  ID único del Flow en Meta. Debes enviar `flow_id` o `flow_name`, pero no ambos.
</ParamField>

<ParamField body="interactive.action.parameters.flow_name" type="string">
  Nombre del Flow en Meta. Alternativa a `flow_id`.
</ParamField>

<ParamField body="interactive.action.parameters.flow_cta" type="string" required>
  Texto del CTA visible para abrir el Flow. Recomendado: 30 caracteres o menos.
</ParamField>

<ParamField body="interactive.action.parameters.flow_token" type="string">
  Token propio de tu negocio para correlacionar la respuesta del Flow.
</ParamField>

<ParamField body="interactive.action.parameters.flow_action" type="string">
  `navigate` o `data_exchange`. Si no lo envías, Meta usa `navigate`.
</ParamField>

<ParamField body="interactive.action.parameters.flow_action_payload.screen" type="string">
  Pantalla inicial del Flow cuando usas `navigate`.
</ParamField>

<ParamField body="interactive.action.parameters.flow_action_payload.data" type="object">
  Datos iniciales que quieres inyectar a la primera pantalla del Flow.
</ParamField>

***

## Próximos Pasos

<CardGroup cols={2}>
  <Card title="Enviar Ubicación" icon="location-dot" href="/api-reference/messages/send-location">
    Comparte coordenadas GPS
  </Card>

  <Card title="Enviar Contactos" icon="address-book" href="/api-reference/messages/send-contacts">
    Comparte tarjetas vCard
  </Card>
</CardGroup>
