# Agente IA Source: https://docs.whaapy.com/api-reference/agent/overview Controla el agente de IA que responde automáticamente a tus clientes El agente de IA de Whaapy responde automáticamente a los mensajes de tus clientes usando tu knowledge base, personalidad configurada y contexto de la conversación. Esta sección documenta los endpoints para **controlar** el agente desde sistemas externos (n8n, Zapier, CRMs, etc.). > [!NOTE] > > Si estás configurando el agente desde el dashboard, empieza por la guía de [Control](/guides/agent-control). Para entender el motor nuevo del agente, lee [colme-2](/concepts/colme-2). ## Guías de configuración > **[Control](/guides/agent-control)** > > Define cuándo responde la IA, cuándo se pausa y cómo convive con humanos. > **[Knowledge](/guides/agent-knowledge)** > > Alimenta al agente con documentos, FAQs, políticas y contexto del negocio. > **[Tools](/guides/agent-tools)** > > Habilita acciones reales como agenda, catálogo, triggers y handoff. > **[Subagents y MCPs](/concepts/subagents-and-tools)** > > Entiende cómo dividir responsabilidades y conectar sistemas externos. ## Endpoints > **[Activar/Desactivar](/api-reference/agent/toggle)** > > Toggle global de la IA para todo el negocio > **[Pausar](/api-reference/agent/pause)** > > Pausa temporal global con reactivación automática > **[Sugerencias](/api-reference/agent/suggestions)** > > Generar respuestas sugeridas sin enviar > **[Control por Conversación](/api-reference/conversations/ai-control)** > > Controlar la IA en conversaciones individuales ## Scopes Requeridos | Endpoint | Scope | | - | - | | Activar/Desactivar | `agent:write` | | Pausar | `agent:write` | | Sugerencias | `conversations:write` | | Control por conversación | `conversations:write` | ## Flujo de Prioridades Cuando llega un mensaje, el agente evalúa los controles en este orden: ```mermaid theme={null} flowchart TD A[Mensaje Recibido] --> B{Pausa Global?} B -->|Sí| X[IA No Responde] B -->|No| C{IA Global ON?} C -->|No| X C -->|Sí| D{Pausa en Conversación?} D -->|Sí| X D -->|No| E{IA ON en Conversación?} E -->|No| X E -->|Sí| F[IA Responde] ``` > [!NOTE] > > Si cualquier nivel está pausado o desactivado, la IA no responderá. La prioridad es: **global > conversación**. ## Casos de Uso > **Handoff a humano** > > Cuando un agente humano toma el control, desactiva la IA para evitar respuestas automáticas. > **Integración con CRM** > > Tu CRM puede pausar la IA mientras procesa tickets o envía respuestas automáticas. > **Modo Copilot** > > Usa sugerencias de IA para asistir a agentes humanos sin enviar automáticamente. > **Automatizaciones** > > n8n/Zapier pueden pausar la IA mientras ejecutan flujos personalizados. # Pausar IA Source: https://docs.whaapy.com/api-reference/agent/pause POST https://api.whaapy.com/agent/v1/pause Pausa el agente de IA globalmente por un tiempo determinado Pausa la IA por un número de minutos. La IA se **reactiva automáticamente** cuando el tiempo expira. > [!NOTE] > > Este es un control global. Para pausar la IA en conversaciones individuales, usa [Control por Conversación](/api-reference/conversations/ai-control). ## Parámetros **duration** `number` *required* Duración de la pausa en minutos. Mínimo: 1, Máximo: 1440 (24 horas) ```bash cURL theme={null} curl -X POST https://api.whaapy.com/agent/v1/pause \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "duration": 30 }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/agent/v1/pause', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ duration: 30 }) }); const data = await response.json(); console.log(data.pausedUntil); // "2026-01-29T15:30:00.000Z" ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/agent/v1/pause', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={'duration': 30} ) print(response.json()) ``` ```json 200 OK theme={null} { "success": true, "pausedUntil": "2026-01-29T15:30:00.000Z", "message": "Agente IA pausado por 30 minutos" } ``` ```json 400 Bad Request theme={null} { "error": "Invalid duration", "message": "La duración debe ser entre 1 y 1440 minutos" } ``` ```json 401 Unauthorized theme={null} { "error": "Unauthorized", "message": "API Key inválida o expirada" } ``` ## Response | Campo | Tipo | Descripción | | - | - | - | | `success` | boolean | `true` si la operación fue exitosa | | `pausedUntil` | string (ISO 8601) | Fecha/hora cuando la pausa expira | | `message` | string | Mensaje descriptivo | ## Scope Requerido Este endpoint requiere el scope `agent:write` en tu API Key. ## Casos de Uso > **Pausar durante horario de oficina** > > Cuando tu equipo está disponible para atender manualmente: > > ```javascript theme={null} > // Pausar por 2 horas (horario de almuerzo) > await fetch('https://api.whaapy.com/agent/v1/pause', { > method: 'POST', > headers: { > 'Authorization': 'Bearer wha_TU_API_KEY', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ duration: 120 }) > }); > ``` > **Integración con n8n/Zapier** > > Pausa la IA mientras tu automatización procesa mensajes: > > ```javascript theme={null} > // Nodo HTTP Request en n8n > // URL: https://api.whaapy.com/agent/v1/pause > // Method: POST > // Body: { "duration": 60 } > ``` > [!TIP] > > La IA se reactiva automáticamente al expirar el tiempo. Si necesitas desactivarla permanentemente, usa [Activar/Desactivar IA](/api-reference/agent/toggle). # Generar Sugerencia Source: https://docs.whaapy.com/api-reference/agent/suggestions POST https://api.whaapy.com/conversations/v1/{id}/ai-suggest Genera una respuesta sugerida por la IA sin enviarla al cliente Genera una respuesta usando el mismo modelo de IA del agente, incluyendo tu knowledge base y personalidad configurada, pero **sin enviar el mensaje**. Útil para: * **Modo Copilot**: Agentes humanos reciben sugerencias para revisar * **Validación**: Verificar respuestas antes de enviarlas automáticamente * **Analytics**: Comparar sugerencias vs respuestas reales ## Parámetros **id** `string` *required* UUID de la conversación ```bash cURL theme={null} curl -X POST https://api.whaapy.com/conversations/v1/550e8400-e29b-41d4-a716-446655440000/ai-suggest \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" ``` ```javascript Node.js theme={null} const conversationId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/ai-suggest`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' } } ); const data = await response.json(); console.log(data.suggestion); ``` ```python Python theme={null} import requests conversation_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.post( f'https://api.whaapy.com/conversations/v1/{conversation_id}/ai-suggest', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' } ) data = response.json() print(data['suggestion']) ``` ```json 200 OK theme={null} { "suggestion": "¡Hola! Gracias por contactarnos. Nuestro horario de atención es de lunes a viernes de 9am a 6pm. ¿En qué puedo ayudarte?", "confidence": "high", "usedKnowledgeBase": true, "documentsCount": 3 } ``` ```json 400 Bad Request theme={null} { "error": "No hay mensajes en esta conversación", "message": "La conversación no tiene historial de mensajes" } ``` ```json 404 Not Found theme={null} { "error": "Conversación no encontrada" } ``` ## Response | Campo | Tipo | Descripción | | - | - | - | | `suggestion` | string | Texto sugerido por la IA | | `confidence` | `"high"` \| `"medium"` \| `"low"` | Nivel de confianza | | `usedKnowledgeBase` | boolean | Si se usaron documentos del knowledge base | | `documentsCount` | number | Cantidad de documentos consultados | ## Niveles de Confianza | Nivel | Score | Significado | | - | - | - | | `high` | > 70% | La IA encontró información relevante | | `medium` | 40-70% | Información parcial disponible | | `low` | \< 40% | Información insuficiente | > [!TIP] > > Si `confidence` es `low`, considera revisar tu knowledge base o agregar más documentos sobre el tema. ## Scope Requerido Este endpoint requiere el scope `conversations:write` en tu API Key. ## Casos de Uso > **Modo Copilot para agentes** > > Muestra sugerencias a agentes humanos: > > ```javascript theme={null} > async function showSuggestion(conversationId) { > const response = await fetch( > `https://api.whaapy.com/conversations/v1/${conversationId}/ai-suggest`, > { > method: 'POST', > headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } > } > ); > > const { suggestion, confidence } = await response.json(); > > displayInUI({ > text: suggestion, > badge: confidence === 'high' ? 'Alta confianza' : 'Revisar' > }); > } > ``` > **Validación antes de enviar** > > Valida la respuesta antes de enviarla: > > ```javascript theme={null} > async function validateAndSend(conversationId) { > const suggestion = await fetch( > `https://api.whaapy.com/conversations/v1/${conversationId}/ai-suggest`, > { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } > ).then(r => r.json()); > > if (suggestion.confidence === 'low') { > await escalateToHuman(conversationId); > return; > } > > await sendMessage(conversationId, suggestion.suggestion); > } > ``` # Activar/Desactivar IA Source: https://docs.whaapy.com/api-reference/agent/toggle POST https://api.whaapy.com/agent/v1/toggle Activa o desactiva el agente de IA globalmente para todo el negocio Activa o desactiva la IA para **todo tu negocio**. Cuando está desactivada, el agente no responderá a ningún mensaje entrante. > [!NOTE] > > Este es un control global. Para controlar la IA en conversaciones individuales, usa [Control por Conversación](/api-reference/conversations/ai-control). ## Parámetros **enabled** `boolean` *required* `true` para activar la IA, `false` para desactivarla ```bash cURL theme={null} curl -X POST https://api.whaapy.com/agent/v1/toggle \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enabled": false }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/agent/v1/toggle', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: false }) }); const data = await response.json(); console.log(data.enabled); // false ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/agent/v1/toggle', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={'enabled': False} ) print(response.json()) ``` ```json 200 OK theme={null} { "success": true, "enabled": false, "message": "Agente IA desactivado" } ``` ```json 401 Unauthorized theme={null} { "error": "Unauthorized", "message": "API Key inválida o expirada" } ``` ```json 403 Forbidden theme={null} { "error": "Forbidden", "message": "Missing required scopes: agent:write" } ``` ## Response | Campo | Tipo | Descripción | | - | - | - | | `success` | boolean | `true` si la operación fue exitosa | | `enabled` | boolean | Estado actual de la IA después del cambio | | `message` | string | Mensaje descriptivo | ## Scope Requerido Este endpoint requiere el scope `agent:write` en tu API Key. ## Caso de Uso > **Desactivar IA durante mantenimiento** > > Cuando necesitas hacer mantenimiento o todo tu equipo está disponible para atender manualmente: > > ```javascript theme={null} > // Desactivar IA > await fetch('https://api.whaapy.com/agent/v1/toggle', { > method: 'POST', > headers: { > 'Authorization': 'Bearer wha_TU_API_KEY', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ enabled: false }) > }); > > // ... realizar mantenimiento ... > > // Reactivar IA > await fetch('https://api.whaapy.com/agent/v1/toggle', { > method: 'POST', > headers: { > 'Authorization': 'Bearer wha_TU_API_KEY', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ enabled: true }) > }); > ``` > [!TIP] > > Si solo necesitas pausar la IA temporalmente, considera usar [Pausar IA](/api-reference/agent/pause) que se reactiva automáticamente. # Autenticación Source: https://docs.whaapy.com/api-reference/authentication Cómo autenticarte con la API de Whaapy # Autenticación La API de Whaapy usa **API Keys** para autenticación. Cada request debe incluir tu API Key en el header `Authorization`. ## Formato del Header ```http theme={null} Authorization: Bearer wha_TU_API_KEY Content-Type: application/json ``` ## Obtener tu API Key **1. Inicia sesión** Ve a [app.whaapy.com](https://app.whaapy.com) e inicia sesión **2. Navega a Configuración** Click en **Configuración** en el menú lateral **3. API Keys** Selecciona la pestaña **API Keys** **4. Genera una nueva key** Click en **Generar Nueva Key** y copia el valor ## Formato de API Key Las API Keys de Whaapy tienen el formato: ``` wha_XXXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` > [!WARNING] > > **Mantén tu API Key segura** > > * Nunca compartas tu API Key públicamente > * No la incluyas en código del lado del cliente (frontend, apps móviles) > * Usa variables de entorno para almacenarla > * Rota tu key periódicamente ## Scopes y Permisos Cada API Key puede tener diferentes scopes que limitan qué acciones puede realizar: | Scope | Permisos | | - | - | | `messages:write` | Enviar mensajes | | `messages:read` | Leer historial de mensajes | | `conversations:read` | Listar conversaciones | | `contacts:read` | Leer contactos | | `contacts:write` | Crear/actualizar contactos | > [!TIP] > > Usa el principio de **mínimo privilegio**: solo otorga los scopes que tu aplicación necesita. ## Ejemplo de Request Autenticado ```bash cURL theme={null} curl -X POST https://api.whaapy.com/messages/v1 \ -H "Authorization: Bearer wha_SUfMm4ZpqYlyvTdtxGU4UjeeNeNYR94T" \ -H "Content-Type: application/json" \ -d '{"to": "+5215512345678", "content": "Hola!"}' ``` ```javascript JavaScript 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', content: 'Hola!' }) }); ``` ```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', 'content': 'Hola!' } ) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/messages/v1'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'to' => '+5215512345678', 'content' => 'Hola!' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` ## Errores de Autenticación ### 401 Unauthorized ```json theme={null} { "error": "unauthorized", "message": "API Key inválida o faltante" } ``` **Causas comunes:** * API Key no incluida en el header * Formato incorrecto (falta `Bearer `) * API Key revocada o expirada ### 403 Forbidden ```json theme={null} { "error": "forbidden", "message": "Sin permisos para este scope" } ``` **Causas comunes:** * La API Key no tiene el scope requerido * Intentando acceder a recursos de otra cuenta ## Variables de Entorno Recomendamos almacenar tu API Key en variables de entorno: ```bash .env theme={null} WHAAPY_API_KEY=wha_TU_API_KEY ``` ```javascript Node.js theme={null} const apiKey = process.env.WHAAPY_API_KEY; ``` ```python Python theme={null} import os api_key = os.environ.get('WHAAPY_API_KEY') ``` ## Rotar API Key Si sospechas que tu API Key fue comprometida: 1. Ve a **Configuración** → **API Keys** 2. Click en **Revocar** junto a la key comprometida 3. Genera una nueva key 4. Actualiza tu aplicación con la nueva key > [!NOTE] > > Revocar una key es instantáneo. Cualquier request con la key revocada fallará inmediatamente. # Operaciones Masivas Source: https://docs.whaapy.com/api-reference/contacts/bulk POST https://api.whaapy.com/contacts/v1/bulk Ejecuta operaciones en múltiples contactos a la vez Realiza operaciones masivas sobre contactos: crear, actualizar, eliminar, gestionar tags y mover en el funnel. > [!WARNING] > > **Límites:** > > * Máximo **100 contactos** por request > * Rate limit: **10 requests/minuto** para operaciones bulk *** ## Operaciones Disponibles | Operación | Descripción | | - | - | | `create` | Crear múltiples contactos | | `update` | Actualizar múltiples contactos | | `delete` | Eliminar múltiples contactos (soft delete) | | `add_tags` | Agregar tags a múltiples contactos | | `remove_tags` | Remover tags de múltiples contactos | | `set_funnel_stage` | Mover contactos a una etapa del funnel | *** ## Body Parameters **operation** `string` *required* Tipo de operación: `create`, `update`, `delete`, `add_tags`, `remove_tags`, `set_funnel_stage` **contacts** `array` **Para `create` y `update`**: Array de objetos de contacto **contact\_ids** `string[]` **Para `delete`, `add_tags`, `remove_tags`, `set_funnel_stage`**: Array de UUIDs **tags** `string[]` **Para `add_tags` y `remove_tags`**: Tags a agregar/remover **funnel\_stage\_id** `string` **Para `set_funnel_stage`**: UUID de la etapa destino *** ## Ejemplos por Operación ### Crear Múltiples Contactos ```bash cURL theme={null} curl -X POST https://api.whaapy.com/contacts/v1/bulk \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "operation": "create", "contacts": [ { "phone_number": "+5215512345678", "name": "Juan", "tags": ["lead"] }, { "phone_number": "+5215587654321", "name": "María", "tags": ["lead"] }, { "phone_number": "+5215511223344", "name": "Pedro", "tags": ["lead"] } ] }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/contacts/v1/bulk', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ operation: 'create', contacts: [ { phone_number: '+5215512345678', name: 'Juan', tags: ['lead'] }, { phone_number: '+5215587654321', name: 'María', tags: ['lead'] }, { phone_number: '+5215511223344', name: 'Pedro', tags: ['lead'] } ] }) }); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/contacts/v1/bulk', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'operation': 'create', 'contacts': [ {'phone_number': '+5215512345678', 'name': 'Juan', 'tags': ['lead']}, {'phone_number': '+5215587654321', 'name': 'María', 'tags': ['lead']}, {'phone_number': '+5215511223344', 'name': 'Pedro', 'tags': ['lead']} ] } ) ``` ### Agregar Tags Masivamente ```json Request theme={null} { "operation": "add_tags", "contact_ids": [ "uuid-1", "uuid-2", "uuid-3" ], "tags": ["campaña-enero", "newsletter"] } ``` ### Remover Tags ```json Request theme={null} { "operation": "remove_tags", "contact_ids": ["uuid-1", "uuid-2"], "tags": ["lead", "sin-atender"] } ``` ### Mover a Etapa del Funnel ```json Request theme={null} { "operation": "set_funnel_stage", "contact_ids": ["uuid-1", "uuid-2"], "funnel_stage_id": "stage-qualified-uuid" } ``` ### Actualizar Múltiples Contactos ```json Request theme={null} { "operation": "update", "contacts": [ { "id": "uuid-1", "add_tags": ["verificado"], "custom_fields": { "verified_at": "2026-01-28" } }, { "id": "uuid-2", "add_tags": ["verificado"], "custom_fields": { "verified_at": "2026-01-28" } } ] } ``` ### Eliminar Múltiples Contactos ```json Request theme={null} { "operation": "delete", "contact_ids": ["uuid-1", "uuid-2", "uuid-3"] } ``` *** ## Respuestas ### Operación Exitosa ```json 200 OK theme={null} { "operation": "add_tags", "success_count": 3, "error_count": 0, "results": [ { "contact_id": "uuid-1", "success": true }, { "contact_id": "uuid-2", "success": true }, { "contact_id": "uuid-3", "success": true } ] } ``` ### Con Errores Parciales ```json 207 Multi-Status theme={null} { "operation": "create", "success_count": 2, "error_count": 1, "results": [ { "phone_number": "+5215512345678", "success": true, "contact_id": "new-uuid-1" }, { "phone_number": "+5215587654321", "success": true, "contact_id": "new-uuid-2" }, { "phone_number": "+5215511223344", "success": false, "error": "duplicate_contact", "existing_contact_id": "existing-uuid" } ] } ``` > [!NOTE] > > El código `207 Multi-Status` indica que algunas operaciones tuvieron éxito y otras fallaron. Revisa el array `results` para ver el detalle de cada una. ### Errores Posibles en Results | Error | Descripción | | - | - | | `not_found` | Contacto no encontrado | | `duplicate_contact` | Ya existe un contacto con ese teléfono | | `invalid_phone_number` | Número de teléfono inválido | | `create_failed` | Error al crear contacto | | `update_failed` | Error al actualizar contacto | | `delete_failed` | Error al eliminar contacto | | `add_tags_failed` | Error al agregar tags | | `remove_tags_failed` | Error al remover tags | | `set_funnel_failed` | Error al asignar etapa del funnel | *** ## Casos de Uso > **Importar contactos de CSV** > > Después de parsear un archivo CSV, crea los contactos en lote: > > ```javascript theme={null} > const contacts = csvRows.map(row => ({ > phone_number: row.telefono, > name: row.nombre, > email: row.email, > tags: ['importado-csv'] > })); > > // Dividir en chunks de 100 > for (let i = 0; i < contacts.length; i += 100) { > await api.post('/contacts/v1/bulk', { > operation: 'create', > contacts: contacts.slice(i, i + 100) > }); > } > ``` > **Etiquetar resultados de campaña** > > Después de enviar una campaña, etiqueta los contactos: > > ```json theme={null} > { > "operation": "add_tags", > "contact_ids": ["...ids de la campaña..."], > "tags": ["campaña-enero-2026", "email-enviado"] > } > ``` > **Limpiar contactos inactivos** > > Elimina contactos que no han interactuado en 1 año: > > ```json theme={null} > { > "operation": "delete", > "contact_ids": ["...ids de búsqueda avanzada..."] > } > ``` *** ## Próximos Pasos > **[Búsqueda Avanzada](/api-reference/contacts/search)** > > Encontrar contactos para operaciones masivas > **[Listar Contactos](/api-reference/contacts/list)** > > Ver contactos actualizados > **[Fusionar Contactos](/api-reference/contacts/merge)** > > Combinar duplicados > **[Listar Tags](/api-reference/contacts/tags)** > > Ver tags disponibles # Crear Contacto Source: https://docs.whaapy.com/api-reference/contacts/create POST https://api.whaapy.com/contacts/v1 Crea un nuevo contacto en tu base de datos Agrega un nuevo contacto a tu CRM de Whaapy. El número de teléfono es el único campo requerido. *** ## Body Parameters **phone\_number** `string` *required* Número de teléfono en formato E.164. Ej: `+5215512345678` **name** `string` Nombre completo del contacto **email** `string` Email del contacto (debe ser válido) **avatar\_url** `string` URL de imagen de perfil **tags** `string[]` Array de tags para categorizar. Ej: `["cliente", "premium"]` **custom\_fields** `object` Campos personalizados como objeto JSON. Ej: `{ "company": "Acme" }` **external\_ids** `object` IDs externos para integración con CRMs. Ej: `{ "hubspot": "abc123" }` **notes** `string` Notas internas sobre el contacto **funnel\_stage\_id** `string` UUID de la etapa del funnel donde colocar el contacto **source** `string` (default: api) Origen del contacto: `api`, `import`, `webhook`, `manual`, etc. **company** `string` Nombre de la empresa **address** `string` Dirección **city** `string` Ciudad **state** `string` Estado/Provincia **postal\_code** `string` Código postal **country** `string` (default: MX) Código de país ISO 3166-1 alpha-2 (2 letras) **assigned\_agent\_id** `string` UUID del agente humano a asignar al contacto. Obtén IDs de agentes con [GET /team/v1](/api-reference/team/list). Las conversaciones futuras del contacto se asignarán automáticamente a este agente. *** ## Ejemplos ### Contacto Básico ```bash cURL theme={null} curl -X POST https://api.whaapy.com/contacts/v1 \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number": "+5215512345678", "name": "Juan Pérez" }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/contacts/v1', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ phone_number: '+5215512345678', name: 'Juan Pérez' }) }); const data = await response.json(); console.log(data.contact); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/contacts/v1', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'phone_number': '+5215512345678', 'name': 'Juan Pérez' } ) print(response.json()) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/contacts/v1'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'phone_number' => '+5215512345678', 'name' => 'Juan Pérez' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` ### Contacto Completo ```json Request theme={null} { "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "tags": ["cliente", "premium"], "custom_fields": { "company": "Acme Inc", "role": "CEO", "contract_value": 50000 }, "external_ids": { "hubspot": "abc123" }, "notes": "Contacto referido por María García", "source": "api", "company": "Acme Inc", "city": "Ciudad de México", "country": "MX" } ``` *** ## Respuesta Exitosa ```json 201 Created theme={null} { "contact": { "id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "avatar_url": null, "tags": ["cliente", "premium"], "custom_fields": { "company": "Acme Inc" }, "external_ids": { "hubspot": "abc123" }, "notes": "Contacto referido por María García", "funnel_stage": null, "source": "api", "company": "Acme Inc", "address": null, "city": "Ciudad de México", "state": null, "postal_code": null, "country": "MX", "assigned_agent_id": null, "last_contact_at": null, "created_at": "2026-01-28T12:00:00Z", "updated_at": "2026-01-28T12:00:00Z" } } ``` *** ## Errores ### Contacto Duplicado ```json 409 Conflict theme={null} { "error": "duplicate_contact", "message": "Ya existe un contacto con este número de teléfono", "existing_contact_id": "550e8400-e29b-41d4-a716-446655440000" } ``` > [!TIP] > > Si recibes este error, puedes usar [PATCH /contacts/v1/:id](/api-reference/contacts/update) para actualizar el contacto existente. ### Datos Inválidos ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "Datos inválidos", "details": { "phone_number": ["Formato inválido. Usar E.164: +5215512345678"], "email": ["Email inválido"] } } ``` *** ## Webhooks Cuando creas un contacto, se dispara el webhook `contact.created`: ```json theme={null} { "event": "contact.created", "data": { "contact_id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "tags": ["cliente", "premium"], "source": "api", "created_at": "2026-01-28T12:00:00Z" } } ``` *** ## Próximos Pasos > **[Operaciones Masivas](/api-reference/contacts/bulk)** > > Crear múltiples contactos a la vez > **[Actualizar Contacto](/api-reference/contacts/update)** > > Modificar contacto existente > **[Enviar Mensaje](/api-reference/messages/send)** > > Enviar mensaje al nuevo contacto > **[Webhooks](/api-reference/webhooks/events)** > > Ver todos los eventos disponibles # Eliminar Contacto Source: https://docs.whaapy.com/api-reference/contacts/delete DELETE https://api.whaapy.com/contacts/v1/{id} Elimina un contacto (soft delete) Elimina un contacto de tu base de datos. Esta operación es un **soft delete**: el contacto se marca como eliminado pero no se borra físicamente. > [!WARNING] > > **Comportamiento de soft delete:** > > * El contacto no aparecerá en listados ni búsquedas > * Las conversaciones históricas se mantienen > * Si el contacto vuelve a escribir por WhatsApp, se restaurará automáticamente *** ## Path Parameters **id** `string` *required* UUID del contacto a eliminar *** ## Ejemplos ```bash cURL theme={null} curl -X DELETE https://api.whaapy.com/contacts/v1/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const contactId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/contacts/v1/${contactId}`, { method: 'DELETE', headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } ); const data = await response.json(); console.log(data.success); // true ``` ```python Python theme={null} import requests contact_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.delete( f'https://api.whaapy.com/contacts/v1/{contact_id}', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```php PHP theme={null} $contactId = '550e8400-e29b-41d4-a716-446655440000'; $ch = curl_init("https://api.whaapy.com/contacts/v1/{$contactId}"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "success": true, "message": "Contacto eliminado", "contact_id": "550e8400-e29b-41d4-a716-446655440000" } ``` *** ## Errores ```json 404 Not Found theme={null} { "error": "not_found", "message": "Contacto no encontrado" } ``` ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "ID de contacto inválido" } ``` *** ## Webhooks Cuando eliminas un contacto, se dispara el webhook `contact.deleted`: ```json theme={null} { "event": "contact.deleted", "data": { "contact_id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "deleted_at": "2026-01-28T12:00:00Z" } } ``` *** ## Restauración Automática > [!NOTE] > > Si un contacto eliminado vuelve a enviar un mensaje por WhatsApp, Whaapy lo restaura automáticamente con todos sus datos anteriores. Esto asegura que: * No pierdas contexto de clientes que vuelven * El historial de conversaciones se mantiene intacto * Los tags y campos personalizados se preservan *** ## Eliminación Masiva Para eliminar múltiples contactos a la vez, usa la [API de operaciones masivas](/api-reference/contacts/bulk): ```json theme={null} { "operation": "delete", "contact_ids": [ "uuid-1", "uuid-2", "uuid-3" ] } ``` *** ## Próximos Pasos > **[Operaciones Masivas](/api-reference/contacts/bulk)** > > Eliminar múltiples contactos > **[Listar Contactos](/api-reference/contacts/list)** > > Ver contactos activos > **[Crear Contacto](/api-reference/contacts/create)** > > Agregar nuevo contacto > **[Webhooks](/api-reference/webhooks/events)** > > Ver eventos disponibles # Listar Campos Personalizados Source: https://docs.whaapy.com/api-reference/contacts/fields GET https://api.whaapy.com/contacts/v1/fields Obtiene las definiciones de campos personalizados configurados Obtén la lista de campos personalizados definidos para tu negocio. Estos campos están tipados y estructurados para consistencia en tu CRM. > [!NOTE] > > Los campos personalizados definidos aquí son diferentes de los `custom_fields` libres que puedes agregar a cualquier contacto. Estos campos tienen tipos específicos y pueden ser requeridos. *** ## Ejemplos ```bash cURL theme={null} curl -X GET https://api.whaapy.com/contacts/v1/fields \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/contacts/v1/fields', { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } }); const data = await response.json(); console.log(data.fields); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/contacts/v1/fields', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/contacts/v1/fields'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "fields": [ { "id": "field-uuid-1", "name": "company", "type": "text", "description": "Nombre de la empresa", "is_required": false, "category": "business" }, { "id": "field-uuid-2", "name": "contract_value", "type": "number", "description": "Valor del contrato en USD", "is_required": false, "category": "sales" }, { "id": "field-uuid-3", "name": "birth_date", "type": "date", "description": "Fecha de nacimiento", "is_required": false, "category": "personal" }, { "id": "field-uuid-4", "name": "website", "type": "url", "description": "Sitio web del contacto", "is_required": false, "category": "business" } ], "total_fields": 4 } ``` *** ## Tipos de Campo | Tipo | Descripción | Ejemplo de valor | | - | - | - | | `text` | Texto libre | `"Acme Corporation"` | | `number` | Valor numérico | `50000` | | `date` | Fecha (ISO 8601) | `"2026-01-28"` | | `email` | Email válido | `"ceo@acme.com"` | | `phone` | Número de teléfono | `"+5215512345678"` | | `url` | URL válida | `"https://acme.com"` | *** ## Campos de la Respuesta | Campo | Tipo | Descripción | | - | - | - | | `fields` | array | Array de definiciones de campos | | `fields[].id` | string | UUID único del campo | | `fields[].name` | string | Nombre del campo (snake\_case) | | `fields[].type` | string | Tipo de dato del campo | | `fields[].description` | string | Descripción del campo | | `fields[].is_required` | boolean | Si el campo es obligatorio | | `fields[].category` | string | Categoría para agrupar campos | | `total_fields` | number | Total de campos definidos | *** ## custom\_fields vs Field Definitions > **custom\_fields (libres)** > > * Objeto JSON en cada contacto > * Sin tipos definidos > * Flexibilidad total > * Ejemplo: `{ "any_key": "any_value" }` > **Field Definitions (tipados)** > > * Definidos a nivel de negocio > * Tipos específicos (text, number, date, etc.) > * Pueden ser requeridos > * Validación automática > [!TIP] > > Usa **Field Definitions** para datos estructurados que necesitan consistencia (valor de contrato, fecha de renovación). Usa **custom\_fields** para datos ad-hoc que varían por contacto. *** ## Próximos Pasos > **[Crear Contacto](/api-reference/contacts/create)** > > Usar campos personalizados > **[Actualizar Contacto](/api-reference/contacts/update)** > > Modificar campos personalizados > **[Búsqueda Avanzada](/api-reference/contacts/search)** > > Filtrar por campos personalizados > **[Listar Tags](/api-reference/contacts/tags)** > > Ver tags disponibles # Obtener Contacto Source: https://docs.whaapy.com/api-reference/contacts/get GET https://api.whaapy.com/contacts/v1/{id} Obtiene los detalles completos de un contacto por su ID Obtén toda la información de un contacto específico, incluyendo sus conversaciones activas. *** ## Path Parameters **id** `string` *required* UUID del contacto *** ## Ejemplos ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/contacts/v1/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const contactId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/contacts/v1/${contactId}`, { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } ); const data = await response.json(); console.log(data.contact); ``` ```python Python theme={null} import requests contact_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.get( f'https://api.whaapy.com/contacts/v1/{contact_id}', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```php PHP theme={null} $contactId = '550e8400-e29b-41d4-a716-446655440000'; $ch = curl_init("https://api.whaapy.com/contacts/v1/{$contactId}"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "contact": { "id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "avatar_url": "https://example.com/avatar.jpg", "tags": ["cliente", "premium"], "custom_fields": { "company": "Acme Inc", "role": "CEO", "contract_value": 50000 }, "external_ids": { "hubspot": "abc123", "salesforce": "xyz789" }, "notes": "Cliente desde 2024. Prefiere contacto por WhatsApp.", "funnel_stage": { "id": "stage-uuid", "name": "Qualified" }, "source": "whaapy", "company": "Acme Inc", "address": "Av. Reforma 123", "city": "Ciudad de México", "state": "CDMX", "postal_code": "06600", "country": "MX", "last_contact_at": "2026-01-28T10:00:00Z", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T10:00:00Z" }, "conversations": [ { "id": "conv-uuid-1", "status": "active", "last_message_at": "2026-01-28T10:00:00Z" }, { "id": "conv-uuid-2", "status": "closed", "last_message_at": "2026-01-15T14:30:00Z" } ] } ``` ### Campos del Contacto | Campo | Tipo | Descripción | | - | - | - | | `id` | string | UUID único del contacto | | `phone_number` | string | Número en formato E.164 | | `name` | string | Nombre completo | | `email` | string | Email de contacto | | `avatar_url` | string | URL de imagen de perfil | | `tags` | string\[] | Array de tags asignados | | `custom_fields` | object | Campos personalizados (clave-valor) | | `external_ids` | object | IDs de CRMs externos | | `notes` | string | Notas internas | | `funnel_stage` | object | Etapa actual del funnel | | `source` | string | Origen del contacto | | `company` | string | Nombre de empresa | | `last_contact_at` | string | Fecha de última interacción | | `created_at` | string | Fecha de creación | | `updated_at` | string | Fecha de última actualización | *** ## Errores ```json 404 Not Found theme={null} { "error": "not_found", "message": "Contacto no encontrado" } ``` ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "ID de contacto inválido" } ``` *** ## Próximos Pasos > **[Actualizar Contacto](/api-reference/contacts/update)** > > Modificar datos del contacto > **[Eliminar Contacto](/api-reference/contacts/delete)** > > Eliminar contacto (soft delete) > **[Fusionar Contactos](/api-reference/contacts/merge)** > > Combinar duplicados > **[Enviar Mensaje](/api-reference/messages/send)** > > Enviar mensaje al contacto # Listar Contactos Source: https://docs.whaapy.com/api-reference/contacts/list GET https://api.whaapy.com/contacts/v1 Obtiene la lista de contactos con filtros y paginación Obtén todos los contactos de tu negocio con soporte para búsqueda, filtros y paginación cursor-based. *** ## Query Parameters **limit** `number` (default: 50) Número de contactos por página (máximo 100) **cursor** `string` Cursor para paginación. Usar `next_cursor` de la respuesta anterior. **search** `string` Búsqueda por nombre, teléfono o email **tags** `string` Filtrar por tags (separados por coma). Ej: `cliente,premium` **funnel\_stage\_id** `string` Filtrar por etapa del funnel (UUID) **source** `string` Filtrar por origen: `whaapy`, `api`, `import`, `webhook`, `manual` **sort\_by** `string` (default: created\_at) Campo para ordenar: `created_at`, `updated_at`, `name`, `last_contact_at` **sort\_order** `string` (default: desc) Orden: `asc` o `desc` *** ## Ejemplos ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/contacts/v1?limit=20&tags=cliente" \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( 'https://api.whaapy.com/contacts/v1?limit=20&tags=cliente', { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } ); const data = await response.json(); console.log(data.contacts); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/contacts/v1', params={'limit': 20, 'tags': 'cliente'}, headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/contacts/v1?limit=20&tags=cliente'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "contacts": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "avatar_url": null, "tags": ["cliente", "premium"], "custom_fields": { "company": "Acme Inc" }, "external_ids": {}, "notes": null, "funnel_stage": { "id": "uuid", "name": "Qualified" }, "source": "whaapy", "company": "Acme Inc", "address": null, "city": "CDMX", "state": null, "postal_code": null, "country": "MX", "last_contact_at": "2026-01-28T10:00:00Z", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T10:00:00Z" } ], "pagination": { "total": 150, "limit": 20, "has_more": true, "next_cursor": "eyJpZCI6Inl5eSJ9" } } ``` ### Campos de Respuesta | Campo | Descripción | | - | - | | `contacts` | Array de objetos de contacto | | `pagination.total` | Total de contactos que coinciden con los filtros | | `pagination.limit` | Límite usado en la consulta | | `pagination.has_more` | Si hay más resultados disponibles | | `pagination.next_cursor` | Cursor para la siguiente página (null si no hay más) | *** ## Paginación Whaapy usa paginación **cursor-based** para eficiencia con grandes volúmenes de datos. **1. Primera página** Haz una request sin cursor: ```bash theme={null} GET /contacts/v1?limit=50 ``` **2. Páginas siguientes** Usa el `next_cursor` de la respuesta anterior: ```bash theme={null} GET /contacts/v1?limit=50&cursor=eyJpZCI6Inl5eSJ9 ``` **3. Fin de resultados** Cuando `has_more` sea `false` y `next_cursor` sea `null`, no hay más resultados. *** ## Próximos Pasos > **[Obtener Contacto](/api-reference/contacts/get)** > > Detalles completos de un contacto > **[Búsqueda Avanzada](/api-reference/contacts/search)** > > Filtros complejos y operadores > **[Crear Contacto](/api-reference/contacts/create)** > > Agregar nuevos contactos > **[Listar Tags](/api-reference/contacts/tags)** > > Ver todos los tags disponibles # Fusionar Contactos Source: https://docs.whaapy.com/api-reference/contacts/merge POST https://api.whaapy.com/contacts/v1/{id}/merge Fusiona un contacto duplicado en otro, conservando la información Combina dos contactos en uno solo. Útil para limpiar duplicados y consolidar información. > [!NOTE] > > El contacto **target** (el de la URL) es el que permanece. El contacto **source** (el del body) será eliminado y sus datos fusionados al target. *** ## Path Parameters **id** `string` *required* UUID del contacto **target** (el que permanece) *** ## Body Parameters **source\_contact\_id** `string` *required* UUID del contacto a fusionar (será eliminado) **merge\_strategy** `object` Estrategia para resolver conflictos de datos *** ## Estrategias de Merge ### Nombre **merge\_strategy.name** `string` (default: prefer\_filled) * `keep_target`: Mantener nombre del target * `keep_source`: Usar nombre del source * `prefer_filled`: Usar el que no esté vacío (prioridad target) ### Tags **merge\_strategy.tags** `string` (default: merge) * `merge`: Combinar tags de ambos contactos * `replace`: Usar solo tags del source * `keep_target`: Mantener tags del target ### Campos Personalizados **merge\_strategy.custom\_fields** `string` (default: merge) * `merge`: Combinar campos (source sobrescribe si hay conflicto) * `replace`: Usar solo campos del source * `keep_target`: Mantener campos del target *** ## Comportamiento Automático Además de la estrategia configurable, el merge automáticamente: * **Transfiere todas las conversaciones** del source al target * **Fusiona external\_ids** (IDs de CRMs externos) * **Concatena las notas** (separadas por `---`) * **Aplica prefer\_filled** para: email, avatar\_url, company, address, city, state, postal\_code * **Elimina (soft delete)** el contacto source *** ## Ejemplos ```bash cURL theme={null} curl -X POST https://api.whaapy.com/contacts/v1/target-uuid/merge \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source_contact_id": "duplicate-uuid", "merge_strategy": { "name": "prefer_filled", "tags": "merge", "custom_fields": "merge" } }' ``` ```javascript Node.js theme={null} const targetId = 'target-uuid'; const response = await fetch( `https://api.whaapy.com/contacts/v1/${targetId}/merge`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ source_contact_id: 'duplicate-uuid', merge_strategy: { name: 'prefer_filled', tags: 'merge', custom_fields: 'merge' } }) } ); ``` ```python Python theme={null} import requests target_id = 'target-uuid' response = requests.post( f'https://api.whaapy.com/contacts/v1/{target_id}/merge', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'source_contact_id': 'duplicate-uuid', 'merge_strategy': { 'name': 'prefer_filled', 'tags': 'merge', 'custom_fields': 'merge' } } ) ``` ### Merge Simple (Estrategia por Defecto) ```json Request theme={null} { "source_contact_id": "duplicate-uuid" } ``` ### Priorizar Datos del Source ```json Request theme={null} { "source_contact_id": "duplicate-uuid", "merge_strategy": { "name": "keep_source", "tags": "replace", "custom_fields": "replace" } } ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "contact": { "id": "target-uuid", "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "tags": ["cliente", "premium", "vip"], "custom_fields": { "company": "Acme", "source_field": "value", "role": "CEO" }, "external_ids": { "hubspot": "abc123", "salesforce": "xyz789" }, "notes": "Notas originales...\n\n---\n\nNotas del contacto fusionado...", "funnel_stage": { "id": "stage-uuid", "name": "Qualified" }, "updated_at": "2026-01-28T12:00:00Z" }, "merged_from": { "id": "duplicate-uuid", "deleted": true }, "changes": { "tags_added": ["vip"], "custom_fields_added": ["source_field", "role"], "conversations_transferred": 2 } } ``` ### Campos de la Respuesta | Campo | Descripción | | - | - | | `contact` | El contacto target actualizado con los datos fusionados | | `merged_from.id` | UUID del contacto source que fue eliminado | | `merged_from.deleted` | Siempre `true` | | `changes.tags_added` | Tags que se agregaron del source | | `changes.custom_fields_added` | Campos personalizados agregados del source | | `changes.conversations_transferred` | Número de conversaciones transferidas | *** ## Errores ```json 404 Not Found theme={null} { "error": "not_found", "message": "Uno o ambos contactos no encontrados" } ``` ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "No se puede fusionar un contacto consigo mismo" } ``` *** ## Webhooks Cuando fusionas contactos, se dispara el webhook `contact.merged`: ```json theme={null} { "event": "contact.merged", "data": { "target_contact_id": "target-uuid", "source_contact_id": "duplicate-uuid", "changes": { "tags_added": ["vip"], "custom_fields_added": ["source_field"], "conversations_transferred": 2 }, "merged_at": "2026-01-28T12:00:00Z" } } ``` *** ## Próximos Pasos > **[Búsqueda Avanzada](/api-reference/contacts/search)** > > Encontrar duplicados > **[Obtener Contacto](/api-reference/contacts/get)** > > Ver contacto fusionado > **[Listar Contactos](/api-reference/contacts/list)** > > Ver lista actualizada > **[Operaciones Masivas](/api-reference/contacts/bulk)** > > Gestionar múltiples contactos # Búsqueda Avanzada Source: https://docs.whaapy.com/api-reference/contacts/search POST https://api.whaapy.com/contacts/v1/search Busca contactos con filtros complejos Realiza búsquedas avanzadas con múltiples filtros y operadores. Ideal para segmentación y análisis. > [!TIP] > > Usa este endpoint cuando necesites filtros más avanzados que los disponibles en `GET /contacts/v1`. Para búsquedas simples, usa el query param `search` en el [listado](/api-reference/contacts/list). *** ## Body Parameters **filters** `object` *required* Objeto con los filtros de búsqueda **sort** `object` Configuración de ordenamiento **limit** `number` (default: 50) Máximo 100 resultados por página **cursor** `string` Cursor para paginación *** ## Filtros Disponibles ### Teléfono **filters.phone\_number.eq** `string` Coincidencia exacta **filters.phone\_number.contains** `string` Contiene substring **filters.phone\_number.starts\_with** `string` Prefijo del número (ej: `52155` para números de CDMX). Se ignoran espacios, guiones, paréntesis, puntos y el `+` antes de comparar. ### Nombre **filters.name.eq** `string` Coincidencia exacta **filters.name.contains** `string` Contiene substring (case-insensitive) **filters.name.starts\_with** `string` Empieza con ### Email **filters.email.eq** `string` Coincidencia exacta **filters.email.contains** `string` Contiene substring **filters.email.domain** `string` Filtrar por dominio (ej: `gmail.com`) ### Tags **filters.tags.all** `string[]` Tiene **TODOS** los tags especificados **filters.tags.any** `string[]` Tiene **AL MENOS UNO** de los tags **filters.tags.none** `string[]` **NO** tiene ninguno de los tags ### Funnel y Origen **filters.funnel\_stage\_id** `string | string[] | null` Etapa del funnel. Acepta: * un UUID: contactos en esa etapa; * un arreglo de UUID (1 a 50): contactos en **cualquiera** de esas etapas; * `null`: contactos **sin etapa**. **filters.source** `string | string[]` Origen(es) del contacto. Puede ser string o array. **filters.source\_not** `string | string[]` Origen **distinto** de estos valores. Los contactos sin origen no coinciden (igual que una comparación SQL `<>`). ### Segmento **filters.segment\_id** `string` UUID de un [segmento](/api-reference/segments/overview) del negocio, estático o dinámico. Devuelve solo los contactos que pertenecen a él y se combina con el resto de filtros. Si el segmento no existe o no es de tu negocio responde `404`; si sus reglas guardadas no son válidas, `400`. ### Fechas **filters.created\_at.after** `string` Creados después de (ISO 8601) **filters.created\_at.before** `string` Creados antes de (ISO 8601) **filters.last\_contact\_at.after** `string` Última interacción después de **filters.last\_contact\_at.before** `string` Última interacción antes de Los filtros de fecha (`created_at`, `updated_at` y `last_contact_at`) también aceptan ventanas móviles relativas a "ahora": **filters.last\_contact\_at.within\_days** `number` Dentro de los últimos N días (entero de 1 a 3650). Ej: `{ "within_days": 7 }` = con actividad esta semana. **filters.last\_contact\_at.older\_than\_days** `number` Hace más de N días (entero de 1 a 3650). Ej: `{ "older_than_days": 30 }` = sin contacto en el último mes. > [!NOTE] > > Los mismos campos `within_days` y `older_than_days` existen en `created_at` y `updated_at`. Como el cálculo es relativo a "ahora", el resultado cambia con el tiempo sin que edites el filtro; por eso son ideales para [segmentos dinámicos](/api-reference/segments/overview). ### Otros **filters.has\_conversation** `boolean` `true` = tiene conversación, `false` = sin conversación **filters.custom\_fields** `object` Filtros en campos personalizados *** ## Ordenamiento **sort.field** `string` (default: created\_at) Campo: `created_at`, `updated_at`, `name`, `last_contact_at`, `phone_number` **sort.order** `string` (default: desc) Orden: `asc` o `desc` *** ## Ejemplos ### Clientes Premium sin Contacto Reciente ```bash cURL theme={null} curl -X POST https://api.whaapy.com/contacts/v1/search \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": { "tags": { "all": ["cliente", "premium"] }, "last_contact_at": { "before": "2026-01-01T00:00:00Z" } }, "sort": { "field": "last_contact_at", "order": "asc" }, "limit": 50 }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/contacts/v1/search', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ filters: { tags: { all: ['cliente', 'premium'] }, last_contact_at: { before: '2026-01-01T00:00:00Z' } }, sort: { field: 'last_contact_at', order: 'asc' }, limit: 50 }) }); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/contacts/v1/search', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'filters': { 'tags': {'all': ['cliente', 'premium']}, 'last_contact_at': {'before': '2026-01-01T00:00:00Z'} }, 'sort': {'field': 'last_contact_at', 'order': 'asc'}, 'limit': 50 } ) ``` ### Contactos por Dominio de Email ```json Request theme={null} { "filters": { "email": { "domain": "empresa.com" }, "has_conversation": true } } ``` ### Leads Nuevos sin Tags ```json Request theme={null} { "filters": { "tags": { "none": ["cliente", "descartado"] }, "created_at": { "after": "2026-01-01T00:00:00Z" } }, "sort": { "field": "created_at", "order": "desc" } } ``` ### Contactos de un Prefijo, en Varias Etapas y sin Actividad Reciente ```json Request theme={null} { "filters": { "phone_number": { "starts_with": "5255" }, "funnel_stage_id": [ "11111111-1111-4111-8111-111111111111", "22222222-2222-4222-8222-222222222222" ], "source_not": "import", "last_contact_at": { "older_than_days": 30 } } } ``` ### Contactos sin Etapa del Funnel ```json Request theme={null} { "filters": { "funnel_stage_id": null } } ``` ### Contactos de Múltiples Orígenes ```json Request theme={null} { "filters": { "source": ["api", "webhook", "import"], "has_conversation": false } } ``` *** ## Respuesta ```json 200 OK theme={null} { "contacts": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "name": "Juan Pérez", "email": "juan@email.com", "tags": ["cliente", "premium"], "funnel_stage": { "id": "uuid", "name": "Qualified" }, "source": "whaapy", "last_contact_at": "2025-12-15T10:00:00Z", "created_at": "2025-06-01T00:00:00Z", "updated_at": "2025-12-15T10:00:00Z" } ], "pagination": { "total": 25, "limit": 50, "has_more": false, "next_cursor": null }, "filters_applied": { "tags": { "all": ["cliente", "premium"] }, "last_contact_at": { "before": "2026-01-01T00:00:00Z" } } } ``` *** ## Casos de Uso > **Reactivación de clientes** > > Encuentra clientes que no han interactuado en los últimos 30 días: > > ```json theme={null} > { > "filters": { > "tags": { "any": ["cliente"] }, > "last_contact_at": { > "before": "2025-12-28T00:00:00Z" > } > } > } > ``` > **Segmentación por empresa** > > Encuentra todos los contactos de un dominio específico: > > ```json theme={null} > { > "filters": { > "email": { "domain": "acme.com" } > } > } > ``` > **Leads sin atender** > > Contactos nuevos sin conversación: > > ```json theme={null} > { > "filters": { > "created_at": { "after": "2026-01-01T00:00:00Z" }, > "has_conversation": false > } > } > ``` *** ## Próximos Pasos > **[Operaciones Masivas](/api-reference/contacts/bulk)** > > Aplicar acciones a los resultados > **[Listar Tags](/api-reference/contacts/tags)** > > Ver tags disponibles para filtrar > **[Webhooks](/api-reference/webhooks/overview)** > > Recibir notificaciones de cambios > **[Enviar Mensaje](/api-reference/messages/send)** > > Contactar a los resultados # Listar Tags Source: https://docs.whaapy.com/api-reference/contacts/tags GET https://api.whaapy.com/contacts/v1/tags Obtiene todos los tags únicos usados en tus contactos Obtén una lista de todos los tags utilizados en tus contactos, ordenados por frecuencia de uso. > [!TIP] > > Usa este endpoint para poblar dropdowns, autocompletado de tags, o para análisis de segmentación. *** ## Ejemplos ```bash cURL theme={null} curl -X GET https://api.whaapy.com/contacts/v1/tags \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/contacts/v1/tags', { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } }); const data = await response.json(); console.log(data.tags); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/contacts/v1/tags', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/contacts/v1/tags'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "tags": [ { "name": "lead", "count": 320 }, { "name": "cliente", "count": 150 }, { "name": "newsletter", "count": 89 }, { "name": "premium", "count": 45 }, { "name": "vip", "count": 12 }, { "name": "campaña-enero", "count": 8 } ], "total_tags": 6 } ``` ### Campos de la Respuesta | Campo | Tipo | Descripción | | - | - | - | | `tags` | array | Array de objetos con nombre y conteo | | `tags[].name` | string | Nombre del tag | | `tags[].count` | number | Número de contactos con este tag | | `total_tags` | number | Total de tags únicos | *** ## Casos de Uso > **Autocompletado en UI** > > Usa la lista de tags para sugerir opciones mientras el usuario escribe: > > ```javascript theme={null} > const { tags } = await api.get('/contacts/v1/tags'); > const suggestions = tags > .filter(t => t.name.includes(userInput)) > .map(t => t.name); > ``` > **Dashboard de segmentación** > > Muestra la distribución de contactos por tag: > > ```javascript theme={null} > const { tags, total_tags } = await api.get('/contacts/v1/tags'); > > // Crear gráfico de barras con tags y conteos > const chartData = tags.map(t => ({ > label: t.name, > value: t.count > })); > ``` > **Validación de tags** > > Verifica que un tag existe antes de usarlo: > > ```javascript theme={null} > const { tags } = await api.get('/contacts/v1/tags'); > const validTags = tags.map(t => t.name); > > const isValid = userTags.every(t => validTags.includes(t)); > ``` *** ## Próximos Pasos > **[Búsqueda por Tags](/api-reference/contacts/search)** > > Filtrar contactos por tags > **[Agregar Tags](/api-reference/contacts/update)** > > Modificar tags de un contacto > **[Tags Masivos](/api-reference/contacts/bulk)** > > Agregar/remover tags en lote > **[Campos Personalizados](/api-reference/contacts/fields)** > > Ver campos disponibles # Actualizar Contacto Source: https://docs.whaapy.com/api-reference/contacts/update PATCH https://api.whaapy.com/contacts/v1/{id} Actualiza los datos de un contacto existente Actualiza uno o más campos de un contacto. Solo envía los campos que quieres modificar. > [!NOTE] > > Los campos no incluidos en la request permanecen sin cambios. *** ## Path Parameters **id** `string` *required* UUID del contacto a actualizar *** ## Body Parameters Todos los campos son opcionales. Solo se actualizan los campos enviados. ### Información Básica **name** `string` Nombre completo **email** `string` Email (debe ser válido) **avatar\_url** `string` URL de imagen de perfil **notes** `string` Notas internas ### Tags **tags** `string[]` **Reemplaza** todos los tags existentes **add\_tags** `string[]` **Agrega** tags sin eliminar los existentes **remove\_tags** `string[]` **Elimina** tags específicos > [!WARNING] > > Si envías `tags`, se reemplazarán todos los tags. Usa `add_tags` o `remove_tags` para modificaciones parciales. ### Campos Personalizados **custom\_fields** `object` Campos personalizados (se fusionan con los existentes) **external\_ids** `object` IDs externos (se fusionan con los existentes) ### Funnel y Origen **funnel\_stage\_id** `string` UUID de nueva etapa del funnel. Envía `null` para quitar la etapa. **source** `string` Origen del contacto ### Información de Empresa **company** `string` Nombre de empresa **address** `string` Dirección **city** `string` Ciudad **state** `string` Estado/Provincia **postal\_code** `string` Código postal **country** `string` Código de país ISO (2 letras) ### Asignación de Agente **assigned\_agent\_id** `string | null` UUID del agente humano a asignar. Obtén IDs con [GET /team/v1](/api-reference/team/list). Envía `null` para quitar la asignación. Al asignar un agente: * Todas las conversaciones **activas** del contacto se reasignan automáticamente * Las conversaciones **futuras** del contacto también se asignarán a este agente *** ## Ejemplos ### Actualizar Nombre y Email ```bash cURL theme={null} curl -X PATCH https://api.whaapy.com/contacts/v1/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Juan Pérez González", "email": "juan.perez@nuevoemail.com" }' ``` ```javascript Node.js theme={null} const contactId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/contacts/v1/${contactId}`, { method: 'PATCH', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Juan Pérez González', email: 'juan.perez@nuevoemail.com' }) } ); const data = await response.json(); ``` ```python Python theme={null} import requests contact_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.patch( f'https://api.whaapy.com/contacts/v1/{contact_id}', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'name': 'Juan Pérez González', 'email': 'juan.perez@nuevoemail.com' } ) ``` ### Agregar Tags (sin eliminar existentes) ```json Request theme={null} { "add_tags": ["vip", "newsletter"] } ``` ### Actualizar Campos Personalizados ```json Request theme={null} { "custom_fields": { "last_purchase": "2026-01-28", "purchase_count": 5 } } ``` > [!NOTE] > > Los `custom_fields` se fusionan con los existentes. Si el contacto tenía `{ "company": "Acme" }` y envías `{ "last_purchase": "2026-01-28" }`, el resultado será `{ "company": "Acme", "last_purchase": "2026-01-28" }`. ### Mover a Nueva Etapa del Funnel ```json Request theme={null} { "funnel_stage_id": "new-stage-uuid" } ``` ### Asignar Agente Humano ```json Request theme={null} { "assigned_agent_id": "550e8400-e29b-41d4-a716-446655440000" } ``` > [!TIP] > > Primero lista los agentes disponibles con [GET /team/v1](/api-reference/team/list) para obtener sus IDs. *** ## Respuesta Exitosa ```json 200 OK theme={null} { "contact": { "id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "name": "Juan Pérez González", "email": "juan.perez@nuevoemail.com", "tags": ["cliente", "premium", "vip"], "custom_fields": { "company": "Acme Inc", "last_purchase": "2026-01-28" }, "funnel_stage": { "id": "new-stage-uuid", "name": "Closed Won" }, "updated_at": "2026-01-28T12:30:00Z" } } ``` *** ## Errores ```json 404 Not Found theme={null} { "error": "not_found", "message": "Contacto no encontrado" } ``` ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "Datos inválidos", "details": { "email": ["Email inválido"] } } ``` *** ## Próximos Pasos > **[Operaciones Masivas](/api-reference/contacts/bulk)** > > Actualizar múltiples contactos > **[Eliminar Contacto](/api-reference/contacts/delete)** > > Eliminar contacto > **[Fusionar Contactos](/api-reference/contacts/merge)** > > Combinar duplicados > **[Listar Tags](/api-reference/contacts/tags)** > > Ver tags disponibles # Control de IA por Conversación Source: https://docs.whaapy.com/api-reference/conversations/ai-control Activa, desactiva o pausa la IA en conversaciones específicas # Control de IA por Conversación Controla el comportamiento de la IA a nivel de conversación individual. Útil cuando un agente humano toma el control o cuando quieres que la IA no responda a ciertos clientes. > **Activar/Desactivar** > > Control permanente de IA > **Pausar Temporalmente** > > Pausa por X minutos *** ## Activar o Desactivar IA Controla permanentemente si la IA puede responder en esta conversación. ``` PATCH https://api.whaapy.com/conversations/v1/{id}/ai ``` ### Parámetros **id** `string` *required* UUID de la conversación **aiEnabled** `boolean` *required* `true` para activar, `false` para desactivar ```bash cURL theme={null} curl -X PATCH "https://api.whaapy.com/conversations/v1/uuid/ai" \ -H "Authorization: Bearer wha_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "aiEnabled": false }' ``` ```javascript Node.js theme={null} const conversationId = 'uuid'; await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/ai`, { method: 'PATCH', headers: { 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ aiEnabled: false }) } ); ``` ```python Python theme={null} import requests conversation_id = 'uuid' response = requests.patch( f'https://api.whaapy.com/conversations/v1/{conversation_id}/ai', headers={ 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, json={'aiEnabled': False} ) ``` ```json 200 OK theme={null} { "message": "Configuración actualizada", "settings": { "aiEnabled": false, "aiMode": "auto", "aiPausedUntil": null } } ``` *** ## Pausar IA Temporalmente Pausa la IA por un tiempo determinado. Útil cuando un agente humano está atendiendo pero quieres que la IA retome automáticamente después. ``` POST https://api.whaapy.com/conversations/v1/{id}/ai/pause ``` ### Parámetros **id** `string` *required* UUID de la conversación **duration** `number` (default: 5) Minutos de pausa (1-1440, máximo 24 horas) ```bash cURL theme={null} curl -X POST "https://api.whaapy.com/conversations/v1/uuid/ai/pause" \ -H "Authorization: Bearer wha_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "duration": 30 }' ``` ```javascript Node.js theme={null} const conversationId = 'uuid'; await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/ai/pause`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ duration: 30 }) } ); ``` ```python Python theme={null} import requests conversation_id = 'uuid' response = requests.post( f'https://api.whaapy.com/conversations/v1/{conversation_id}/ai/pause', headers={ 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, json={'duration': 30} ) ``` ```json 200 OK theme={null} { "success": true, "pausedUntil": "2026-01-29T11:00:00Z", "message": "IA pausada por 30 minutos" } ``` > [!TIP] > > Usa la pausa temporal cuando un agente humano está atendiendo pero quieres que la IA retome automáticamente después. Si quieres deshabilitar la IA permanentemente, usa el endpoint `PATCH /ai` con `aiEnabled: false`. *** ## Prioridad de control La IA responde a un mensaje **solo si TODOS estos niveles están activos**: ```mermaid theme={null} flowchart TD A[Mensaje entrante] --> B{IA Global activa?} B -->|No| X[No responde] B -->|Sí| C{IA pausada globalmente?} C -->|Sí| X C -->|No| D{IA activa en conversación?} D -->|No| X D -->|Sí| E{IA pausada en conversación?} E -->|Sí| X E -->|No| F[IA responde] ``` ### Niveles de control | Nivel | Endpoint | Prioridad | | - | - | - | | Global (todo el negocio) | `POST /agent/toggle` | Más alta | | Pausa global | `POST /agent/pause` | Alta | | Por conversación | `PATCH /conversations/v1/:id/ai` | Media | | Pausa por conversación | `POST /conversations/v1/:id/ai/pause` | Baja | > [!WARNING] > > Si la IA está desactivada globalmente (`POST /agent/toggle`), no responderá aunque esté activada a nivel de conversación. *** ## Casos de uso > **Agente humano toma el control** > > Cuando un agente humano comienza a atender, pausa la IA: > > ```javascript theme={null} > // Webhook: agente asignado a conversación > async function onAgentAssigned(conversationId) { > await fetch( > `https://api.whaapy.com/conversations/v1/${conversationId}/ai/pause`, > { > method: 'POST', > headers: { > 'Authorization': 'Bearer wha_xxxxx', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ duration: 60 }) // 1 hora > } > ); > } > ``` > **Desactivar IA para cliente VIP** > > Algunos clientes prefieren atención humana exclusiva: > > ```javascript theme={null} > async function disableAIForVIP(conversationId) { > await fetch( > `https://api.whaapy.com/conversations/v1/${conversationId}/ai`, > { > method: 'PATCH', > headers: { > 'Authorization': 'Bearer wha_xxxxx', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ aiEnabled: false }) > } > ); > } > ``` > **Reactivar IA después de resolver** > > Cuando el agente termina, reactiva la IA: > > ```javascript theme={null} > async function onTicketResolved(conversationId) { > await fetch( > `https://api.whaapy.com/conversations/v1/${conversationId}/ai`, > { > method: 'PATCH', > headers: { > 'Authorization': 'Bearer wha_xxxxx', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ aiEnabled: true }) > } > ); > } > ``` *** ## Control global vs por conversación | Característica | Global | Por conversación | | - | - | - | | Afecta a | Todas las conversaciones | Solo una conversación | | Endpoint | `POST /agent/toggle` | `PATCH /conversations/v1/:id/ai` | | Caso de uso | Mantenimiento, emergencias | Clientes específicos, handoff | | Pausa temporal | `POST /agent/pause` | `POST /conversations/v1/:id/ai/pause` | > [!NOTE] > > Para control global de la IA, consulta la documentación de [Agente IA](/api-reference/agent/overview). # Archivar Conversación Source: https://docs.whaapy.com/api-reference/conversations/archive POST https://api.whaapy.com/conversations/v1/{id}/archive Archiva una conversación para ocultarla de la bandeja principal ## Parámetros **id** `string` *required* UUID de la conversación ```bash cURL theme={null} curl -X POST "https://api.whaapy.com/conversations/v1/uuid/archive" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const conversationId = 'uuid'; await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/archive`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); ``` ```python Python theme={null} import requests conversation_id = 'uuid' response = requests.post( f'https://api.whaapy.com/conversations/v1/{conversation_id}/archive', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```json 200 OK theme={null} { "message": "Conversación archivada exitosamente", "advisorsToAnalyze": 1 } ``` ## Diferencia entre cerrar y archivar | Acción | Estado | Visible en bandeja | Se reabre con mensaje | | - | - | - | - | | Cerrar | `closed` | Sí (sección cerradas) | Sí | | Archivar | `archived` | No (sección archivo) | Sí | > [!NOTE] > > Usa **archivar** para conversaciones que no quieres ver en la bandeja principal pero quieres conservar el historial. Usa **cerrar** para conversaciones resueltas que aún quieres ver en la lista de cerradas. ## Efectos 1. **Cambia el estado** a `archived` 2. **Oculta de la bandeja principal** (solo visible en filtro "Archivadas") 3. **Analiza performance** de agentes que participaron 4. **Se reabre automáticamente** si el contacto envía un nuevo mensaje # Buscar por Teléfono Source: https://docs.whaapy.com/api-reference/conversations/by-phone GET https://api.whaapy.com/conversations/v1/by-phone/{phone} Busca una conversación por número de teléfono ## Parámetros de Path **phone** `string` *required* Número de teléfono en formato E.164 (ej: `+5215512345678`) > [!TIP] > > El número se normaliza automáticamente. Puedes enviar `5215512345678`, `+5215512345678`, o `521 55 1234 5678`. ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/conversations/v1/by-phone/+5215512345678" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const phone = '+5215512345678'; const response = await fetch( `https://api.whaapy.com/conversations/v1/by-phone/${encodeURIComponent(phone)}`, { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); const data = await response.json(); ``` ```python Python theme={null} import requests from urllib.parse import quote phone = '+5215512345678' response = requests.get( f'https://api.whaapy.com/conversations/v1/by-phone/{quote(phone, safe="")}', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```php PHP theme={null} $phone = urlencode('+5215512345678'); $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/conversations/v1/by-phone/{$phone}", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "phoneNumber": "+5215512345678", "contactName": "Juan Pérez", "profilePictureUrl": "https://...", "lastMessageAt": "2026-01-29T10:30:00Z", "lastMessagePreview": "Hola, ¿tienen disponibilidad?", "unreadCount": 2, "status": "active", "contact": { "id": "uuid", "name": "Juan Pérez", "email": "juan@ejemplo.com", "phoneNumber": "+5215512345678", "avatarUrl": null, "tags": ["cliente-nuevo"], "customFields": {}, "funnelStage": { "id": "uuid", "name": "Interesado", "color": "#3B82F6", "position": 1 } }, "settings": { "aiEnabled": true, "aiMode": "auto", "aiPausedUntil": null, "pausedBy": null }, "createdAt": "2026-01-28T08:00:00Z", "updatedAt": "2026-01-29T10:30:00Z" } } ``` ## Errores ```json 404 Not Found theme={null} { "error": "Not found", "message": "Conversación no encontrada para este número de teléfono" } ``` > [!NOTE] > > Este endpoint es útil para integraciones donde tienes el número de teléfono pero no el ID de la conversación. Por ejemplo, cuando recibes un webhook de tu CRM con el teléfono del cliente. ## Casos de uso > **Sincronizar desde CRM** > > Cuando tu CRM te notifica sobre un cliente, busca su conversación por teléfono: > > ```javascript theme={null} > // Webhook de HubSpot/Salesforce > const phoneFromCRM = '+5215512345678'; > const conversation = await fetch( > `https://api.whaapy.com/conversations/v1/by-phone/${encodeURIComponent(phoneFromCRM)}`, > { headers: { 'Authorization': 'Bearer wha_xxxxx' } } > ).then(r => r.json()); > > if (conversation.data) { > console.log('Conversación encontrada:', conversation.data.id); > } > ``` > **Verificar si existe conversación** > > Antes de iniciar una nueva conversación, verifica si ya existe una: > > ```javascript theme={null} > async function getOrCreateConversation(phone) { > // Primero buscar existente > const existing = await fetch( > `https://api.whaapy.com/conversations/v1/by-phone/${encodeURIComponent(phone)}`, > { headers: { 'Authorization': 'Bearer wha_xxxxx' } } > ).then(r => r.ok ? r.json() : null); > > if (existing?.data) { > return existing.data; > } > > // Si no existe, enviar mensaje para crear > const newMessage = await fetch('https://api.whaapy.com/messages/v1', { > method: 'POST', > headers: { > 'Authorization': 'Bearer wha_xxxxx', > 'Content-Type': 'application/json' > }, > body: JSON.stringify({ > to: phone, > type: 'template', > template: { name: 'hello_world', language: 'es_MX' } > }) > }).then(r => r.json()); > > return newMessage.data; > } > ``` # Cerrar Conversación Source: https://docs.whaapy.com/api-reference/conversations/close POST https://api.whaapy.com/conversations/v1/{id}/close Cierra una conversación activa ## Parámetros **id** `string` *required* UUID de la conversación ```bash cURL theme={null} curl -X POST "https://api.whaapy.com/conversations/v1/uuid/close" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const conversationId = 'uuid'; await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/close`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); ``` ```python Python theme={null} import requests conversation_id = 'uuid' response = requests.post( f'https://api.whaapy.com/conversations/v1/{conversation_id}/close', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```php PHP theme={null} $conversationId = 'uuid'; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/conversations/v1/{$conversationId}/close", CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "message": "Conversación cerrada exitosamente", "advisorsToAnalyze": 1 } ``` ## Efectos Cerrar una conversación tiene los siguientes efectos: 1. **Cambia el estado** a `closed` 2. **Dispara webhook** `conversation.closed` (si tienes webhooks configurados) 3. **Analiza performance** de los agentes humanos que participaron 4. **La conversación puede reabrirse** si el contacto envía un nuevo mensaje > [!NOTE] > > El campo `advisorsToAnalyze` indica cuántos agentes humanos participaron y serán analizados para métricas de performance. ## Errores ```json 404 Not Found theme={null} { "error": "Not found", "message": "Conversación no encontrada" } ``` ## Casos de uso > **Cerrar después de resolver ticket** > > Cuando un ticket se resuelve en tu sistema de soporte, cierra la conversación: > > ```javascript theme={null} > // Webhook de Zendesk/Freshdesk > async function onTicketResolved(ticketData) { > const phone = ticketData.customer.phone; > > // Buscar conversación por teléfono > const conversation = await fetch( > `https://api.whaapy.com/conversations/v1/by-phone/${encodeURIComponent(phone)}`, > { headers: { 'Authorization': 'Bearer wha_xxxxx' } } > ).then(r => r.json()); > > if (conversation.data) { > // Cerrar conversación > await fetch( > `https://api.whaapy.com/conversations/v1/${conversation.data.id}/close`, > { > method: 'POST', > headers: { 'Authorization': 'Bearer wha_xxxxx' } > } > ); > } > } > ``` > **Cerrar conversaciones inactivas** > > Cierra automáticamente conversaciones sin actividad: > > ```javascript theme={null} > // Cron job diario > async function closeInactiveConversations() { > // Obtener conversaciones inactivas por 7 días > const inactive = await fetch( > 'https://api.whaapy.com/conversations/v1?filters=' + > encodeURIComponent(JSON.stringify({ inactiveDays: 7 })), > { headers: { 'Authorization': 'Bearer wha_xxxxx' } } > ).then(r => r.json()); > > for (const conv of inactive.data) { > await fetch( > `https://api.whaapy.com/conversations/v1/${conv.id}/close`, > { > method: 'POST', > headers: { 'Authorization': 'Bearer wha_xxxxx' } > } > ); > } > } > ``` # Obtener Conversación Source: https://docs.whaapy.com/api-reference/conversations/get GET https://api.whaapy.com/conversations/v1/{id} Obtén los detalles de una conversación específica ## Parámetros de Path **id** `string` *required* UUID de la conversación ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/conversations/v1/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const conversationId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}`, { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); const data = await response.json(); ``` ```python Python theme={null} import requests conversation_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.get( f'https://api.whaapy.com/conversations/v1/{conversation_id}', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```php PHP theme={null} $conversationId = '550e8400-e29b-41d4-a716-446655440000'; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/conversations/v1/{$conversationId}", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "phoneNumber": "+5215512345678", "contactName": "Juan Pérez", "profilePictureUrl": "https://...", "lastMessageAt": "2026-01-29T10:30:00Z", "unreadCount": 2, "status": "active", "settings": { "aiEnabled": true, "aiMode": "auto", "aiPausedUntil": null, "pausedBy": null }, "contact": { "id": "uuid", "phoneNumber": "+5215512345678", "name": "Juan Pérez", "email": "juan@ejemplo.com", "avatarUrl": null, "tags": ["cliente-nuevo", "vip"], "funnelStage": { "id": "uuid", "name": "Calificado", "color": "#10B981", "position": 2 } }, "assignedTo": { "agentId": "uuid", "agentName": "María García", "agentEmail": "maria@empresa.com", "assignedAt": "2026-01-28T12:00:00Z", "assignmentMethod": "manual" }, "stats": { "totalMessages": 45, "unreadMessages": 2 }, "createdAt": "2026-01-28T08:00:00Z", "updatedAt": "2026-01-29T10:30:00Z" } } ``` ## Campos de respuesta | Campo | Tipo | Descripción | | - | - | - | | `id` | string | UUID de la conversación | | `phoneNumber` | string | Número de WhatsApp del contacto | | `contactName` | string | Nombre del contacto | | `profilePictureUrl` | string | URL de la foto de perfil | | `lastMessageAt` | string | Fecha del último mensaje | | `unreadCount` | number | Mensajes no leídos | | `status` | string | `active`, `closed`, `archived` | | `settings.aiEnabled` | boolean | Si la IA está activa | | `settings.aiMode` | string | `auto` o `manual` | | `settings.aiPausedUntil` | string | Fecha hasta la que la IA está pausada | | `contact` | object | Datos completos del contacto | | `assignedTo` | object | Agente asignado (null si no hay) | | `stats.totalMessages` | number | Total de mensajes | | `stats.unreadMessages` | number | Mensajes sin leer | ## Errores ```json 404 Not Found theme={null} { "error": "Not found", "message": "Conversación no encontrada" } ``` # Listar Conversaciones Source: https://docs.whaapy.com/api-reference/conversations/list GET https://api.whaapy.com/conversations/v1 Obtén todas las conversaciones con filtros y paginación ## Parámetros de Query **status** `string` (default: all) Filtrar por estado: `all`, `active`, `closed`, `archived` **search** `string` Buscar por nombre de contacto, teléfono o email **limit** `number` (default: 20) Número de resultados (máximo 100) **offset** `number` (default: 0) Offset para paginación **filters** `string` Filtros avanzados en formato JSON (ver ejemplos abajo) ## Filtros avanzados El parámetro `filters` acepta un JSON URL-encoded con las siguientes opciones: ```json theme={null} { "statusFilter": "unread", // unread, ai-paused, ai-active, needs-attention "stageId": "uuid", // Etapa del funnel "tags": ["vip", "nuevo"], // Tags del contacto (OR) "assignedTo": "me", // "me", "none", o UUID de agente "updatedFilter": "today", // today, week, month "inactiveDays": 7 // Días sin actividad } ``` | Filtro | Valores | Descripción | | - | - | - | | `statusFilter` | `unread`, `ai-paused`, `ai-active`, `needs-attention` | Estado de la conversación | | `stageId` | UUID | Etapa del funnel de ventas | | `tags` | Array de strings | Tags del contacto (match OR) | | `assignedTo` | `me`, `none`, UUID | Asignación del agente | | `updatedFilter` | `today`, `week`, `month` | Filtro de tiempo | | `inactiveDays` | 1-365 | Días de inactividad | ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/conversations/v1?status=active&limit=10" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const response = await fetch( 'https://api.whaapy.com/conversations/v1?status=active&limit=10', { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/conversations/v1', params={'status': 'active', 'limit': 10}, headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```php PHP theme={null} $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/conversations/v1?status=active&limit=10", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "phoneNumber": "+5215512345678", "contactName": "Juan Pérez", "profilePictureUrl": "https://...", "lastMessageAt": "2026-01-29T10:30:00Z", "lastMessagePreview": "Hola, ¿tienen disponibilidad?", "unreadCount": 2, "status": "active", "contact": { "id": "uuid", "name": "Juan Pérez", "email": "juan@ejemplo.com", "tags": ["cliente-nuevo"], "funnelStage": { "id": "uuid", "name": "Interesado", "color": "#3B82F6" } }, "settings": { "aiEnabled": true, "aiPausedUntil": null }, "assignedTo": { "agentId": "uuid", "agentName": "María García" }, "createdAt": "2026-01-28T08:00:00Z" } ], "meta": { "total": 150, "limit": 10, "offset": 0, "hasMore": true } } ``` ## Campos de respuesta | Campo | Tipo | Descripción | | - | - | - | | `id` | string | UUID de la conversación | | `phoneNumber` | string | Número de WhatsApp del contacto | | `contactName` | string | Nombre del contacto | | `lastMessageAt` | string | Fecha del último mensaje (ISO 8601) | | `lastMessagePreview` | string | Preview del último mensaje (truncado) | | `unreadCount` | number | Mensajes no leídos | | `status` | string | Estado: `active`, `closed`, `archived` | | `contact` | object | Datos del contacto asociado | | `settings` | object | Configuración de IA para esta conversación | | `assignedTo` | object | Agente asignado (si aplica) | | `meta.total` | number | Total de conversaciones que coinciden | | `meta.hasMore` | boolean | Si hay más páginas disponibles | > [!TIP] > > Usa `filters` con `assignedTo: "none"` para encontrar conversaciones sin asignar que necesitan atención. ## Errores comunes ```json 401 Unauthorized theme={null} { "error": "Missing API Key", "message": "Authorization header required. Format: \"Bearer wha_xxxxx\"" } ``` ```json 403 Forbidden theme={null} { "error": "Insufficient permissions", "message": "This API Key does not have the required scope: conversations:read" } ``` # Marcar como Leída Source: https://docs.whaapy.com/api-reference/conversations/mark-read PATCH https://api.whaapy.com/conversations/v1/{id}/mark-read Marca todos los mensajes de una conversación como leídos ## Parámetros **id** `string` *required* UUID de la conversación ```bash cURL theme={null} curl -X PATCH "https://api.whaapy.com/conversations/v1/uuid/mark-read" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const conversationId = 'uuid'; await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/mark-read`, { method: 'PATCH', headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); ``` ```python Python theme={null} import requests conversation_id = 'uuid' response = requests.patch( f'https://api.whaapy.com/conversations/v1/{conversation_id}/mark-read', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```json 200 OK theme={null} { "message": "Conversación marcada como leída", "markedMessages": 5 } ``` ## Campos de respuesta | Campo | Tipo | Descripción | | - | - | - | | `message` | string | Confirmación de la acción | | `markedMessages` | number | Cantidad de mensajes marcados como leídos | ## Efectos 1. **Resetea `unreadCount`** a 0 2. **Marca todos los mensajes entrantes** como `status: 'read'` 3. **NO envía confirmación de lectura** a WhatsApp (esto es automático cuando el usuario ve el mensaje en la app) > [!NOTE] > > Este endpoint solo actualiza el estado interno de Whaapy. Los "checks azules" de WhatsApp se envían automáticamente cuando el mensaje se visualiza en la interfaz del dashboard. ## Casos de uso > **Sincronizar estado con CRM** > > Cuando procesas una conversación en tu CRM, márcala como leída: > > ```javascript theme={null} > async function processConversationInCRM(conversationId) { > // ... procesar en CRM ... > > // Marcar como leída en Whaapy > await fetch( > `https://api.whaapy.com/conversations/v1/${conversationId}/mark-read`, > { > method: 'PATCH', > headers: { 'Authorization': 'Bearer wha_xxxxx' } > } > ); > } > ``` # Historial de Mensajes Source: https://docs.whaapy.com/api-reference/conversations/messages GET https://api.whaapy.com/conversations/v1/{id}/messages Obtén el historial completo de mensajes de una conversación ## Parámetros **id** `string` *required* UUID de la conversación **limit** `number` (default: 50) Número de mensajes (máximo 100) **before** `string` Cursor para paginación (ID del mensaje más antiguo) ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/conversations/v1/uuid/messages?limit=20" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const conversationId = 'uuid'; const response = await fetch( `https://api.whaapy.com/conversations/v1/${conversationId}/messages?limit=20`, { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); const data = await response.json(); ``` ```python Python theme={null} import requests conversation_id = 'uuid' response = requests.get( f'https://api.whaapy.com/conversations/v1/{conversation_id}/messages', params={'limit': 20}, headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```php PHP theme={null} $conversationId = 'uuid'; $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/conversations/v1/{$conversationId}/messages?limit=20", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "data": [ { "id": "msg-uuid-1", "conversationId": "conv-uuid", "content": "Hola, ¿tienen disponibilidad para mañana?", "messageType": "text", "direction": "inbound", "status": "read", "sentByAi": false, "timestamp": "2026-01-29T10:30:00Z", "createdAt": "2026-01-29T10:30:00Z" }, { "id": "msg-uuid-2", "conversationId": "conv-uuid", "content": "¡Hola! Sí, tenemos disponibilidad. ¿A qué hora te gustaría?", "messageType": "text", "direction": "outbound", "status": "delivered", "sentByAi": true, "timestamp": "2026-01-29T10:30:15Z", "createdAt": "2026-01-29T10:30:15Z" }, { "id": "msg-uuid-3", "conversationId": "conv-uuid", "content": null, "messageType": "image", "direction": "inbound", "status": "read", "sentByAi": false, "mediaUrl": "https://api.whaapy.com/messages/msg-uuid-3/media?token=xxx", "mediaMimeType": "image/jpeg", "timestamp": "2026-01-29T10:31:00Z", "createdAt": "2026-01-29T10:31:00Z" } ], "meta": { "hasMore": true, "oldestMessageId": "msg-uuid-1" } } ``` ## Campos del mensaje | Campo | Tipo | Descripción | | - | - | - | | `id` | string | UUID del mensaje | | `conversationId` | string | UUID de la conversación | | `content` | string | Contenido del mensaje (null para media) | | `messageType` | string | Tipo de mensaje (ver tabla abajo) | | `direction` | string | `inbound` (recibido) o `outbound` (enviado) | | `status` | string | `sent`, `delivered`, `read`, `failed` | | `sentByAi` | boolean | Si fue enviado por la IA | | `mediaUrl` | string | URL del archivo (si es media) | | `mediaMimeType` | string | MIME type del archivo | | `timestamp` | string | Fecha y hora del mensaje | | `createdAt` | string | Fecha de creación en BD | ## Tipos de mensaje | Tipo | Descripción | | - | - | | `text` | Mensaje de texto simple | | `image` | Imagen (jpeg, png, webp) | | `video` | Video (mp4) | | `audio` | Audio o nota de voz (ogg, mp3) | | `document` | Documento (pdf, doc, etc.) | | `sticker` | Sticker de WhatsApp | | `template` | Mensaje de template | | `interactive` | Mensaje interactivo (botones, listas) | | `location` | Ubicación | | `contacts` | Tarjeta de contacto | | `reaction` | Reacción a mensaje | ## Paginación Los mensajes se ordenan del más reciente al más antiguo. Para obtener mensajes más antiguos, usa el parámetro `before` con el `id` del mensaje más antiguo de la página actual. ```javascript theme={null} // Primera página const page1 = await fetch( `https://api.whaapy.com/conversations/v1/${id}/messages?limit=20`, { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ).then(r => r.json()); // Segunda página (mensajes más antiguos) if (page1.meta.hasMore) { const oldestId = page1.meta.oldestMessageId; const page2 = await fetch( `https://api.whaapy.com/conversations/v1/${id}/messages?limit=20&before=${oldestId}`, { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ).then(r => r.json()); } ``` > [!WARNING] > > Los mensajes se ordenan del más reciente al más antiguo. Usa `before` para paginar hacia atrás en el historial. ## Acceder a media Las URLs de media (`mediaUrl`) son temporales y requieren el token incluido. Para acceder a la media: ```javascript theme={null} // La URL ya incluye el token const mediaUrl = message.mediaUrl; const mediaResponse = await fetch(mediaUrl); const blob = await mediaResponse.blob(); ``` > [!TIP] > > Las URLs de media expiran después de 5 minutos. Si necesitas acceso persistente, descarga el archivo y almacénalo en tu propio servidor. # Conversaciones Source: https://docs.whaapy.com/api-reference/conversations/overview Gestiona conversaciones de WhatsApp programáticamente # Conversaciones Las conversaciones representan hilos de chat con tus contactos. Cada conversación está vinculada a un contacto y contiene todo el historial de mensajes. > **[Listar Conversaciones](/api-reference/conversations/list)** > > Obtén todas las conversaciones con filtros avanzados > **[Obtener Conversación](/api-reference/conversations/get)** > > Detalles de una conversación específica > **[Buscar por Teléfono](/api-reference/conversations/by-phone)** > > Encuentra una conversación por número > **[Historial de Mensajes](/api-reference/conversations/messages)** > > Obtén todos los mensajes de una conversación > **[Cerrar Conversación](/api-reference/conversations/close)** > > Cierra una conversación activa > **[Control de IA](/api-reference/conversations/ai-control)** > > Activa, desactiva o pausa la IA por conversación ## Scopes requeridos | Endpoint | Scope | | - | - | | Lectura (GET) | `conversations:read` | | Escritura (POST/PATCH) | `conversations:write` | | Mensajes | `messages:read` | ## Estados de conversación | Estado | Descripción | | - | - | | `active` | Conversación abierta y activa | | `closed` | Cerrada manualmente o por inactividad | | `archived` | Archivada (oculta de la bandeja principal) | ## Casos de uso comunes > **Sincronizar conversaciones con CRM** > > Usa `GET /conversations/v1` con filtros de fecha para obtener conversaciones nuevas y sincronizarlas con tu CRM. > > ```bash theme={null} > curl "https://api.whaapy.com/conversations/v1?filters=%7B%22updatedFilter%22%3A%22today%22%7D" \ > -H "Authorization: Bearer wha_xxxxx" > ``` > **Detectar conversaciones sin responder** > > Filtra por mensajes no leídos y estado activo para encontrar conversaciones pendientes. > > ```bash theme={null} > curl "https://api.whaapy.com/conversations/v1?filters=%7B%22statusFilter%22%3A%22unread%22%7D" \ > -H "Authorization: Bearer wha_xxxxx" > ``` > **Automatizar cierre de conversaciones** > > Usa `POST /conversations/v1/:id/close` después de resolver un ticket en tu sistema. > > ```bash theme={null} > curl -X POST "https://api.whaapy.com/conversations/v1/uuid/close" \ > -H "Authorization: Bearer wha_xxxxx" > ``` > **Pausar IA al tomar control manual** > > Cuando un agente humano toma el control, pausa la IA temporalmente. > > ```bash theme={null} > curl -X POST "https://api.whaapy.com/conversations/v1/uuid/ai/pause" \ > -H "Authorization: Bearer wha_xxxxx" \ > -H "Content-Type: application/json" \ > -d '{"duration": 30}' > ``` ## Próximos pasos > **[Enviar Mensajes](/api-reference/messages/send)** > > Envía mensajes a conversaciones existentes > **[Webhooks](/api-reference/webhooks/overview)** > > Recibe notificaciones de nuevos mensajes # Códigos de Error Source: https://docs.whaapy.com/api-reference/errors Referencia completa de errores de la API de Whaapy y WhatsApp # Códigos de Error Cuando un mensaje falla, Whaapy devuelve información detallada sobre el error incluyendo el código de Meta/WhatsApp, una descripción y pasos para solucionarlo. ## Estructura de Error ```json theme={null} { "error": "message_delivery_failed", "message": "Descripción legible del error", "code": "131047", "action_required": "Acción sugerida", "instructions": "Pasos detallados para resolver", "data": { "id": "uuid-del-mensaje", "status": "failed" } } ``` *** ## Errores HTTP de Whaapy | Código | Significado | Descripción | | - | - | - | | `200` | OK | Mensaje enviado exitosamente | | `400` | Bad Request | Error de validación en el request | | `401` | Unauthorized | API Key inválida o faltante | | `403` | Forbidden | Sin permisos para este scope | | `404` | Not Found | Recurso no encontrado | | `429` | Too Many Requests | Rate limit excedido | | `500` | Internal Server Error | Error interno del servidor | *** ## Errores de WhatsApp (Meta) Estos códigos provienen directamente de la API de WhatsApp Cloud y se incluyen en el campo `code` de la respuesta. ### 131047 - Ventana de 24 Horas Expirada > [!WARNING] > > Este es el error más común. WhatsApp solo permite enviar mensajes de texto libre dentro de las 24 horas posteriores al último mensaje del usuario. **Causa**: Intentaste enviar un mensaje de texto, imagen, video, etc. a un usuario que no te ha escrito en las últimas 24 horas. **Solución**: 1. Envía un **template message** pre-aprobado por Meta 2. Espera a que el usuario te escriba primero 3. Usa el endpoint `/retry` después de que se reabra la ventana ```json Ejemplo de respuesta theme={null} { "error": "message_delivery_failed", "message": "La ventana de conversación de 24 horas ha expirado", "code": "131047", "action_required": "Usar template message", "instructions": "Para contactar usuarios después de 24h sin respuesta, debes enviar un template aprobado por Meta", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "failed" } } ``` > [!TIP] > > El mensaje se guarda en Whaapy con `status: failed`. Una vez que la ventana se reabra (el usuario responde a tu template), puedes usar `POST /messages/v1/{id}/retry` para reenviar el mensaje original. *** ### 131042 - Pago Requerido **Causa**: Tu cuenta de Meta Business no tiene un método de pago configurado o el método de pago falló. **Solución**: 1. Ve a [Meta Business Suite](https://business.facebook.com) 2. Navega a **Configuración** → **Pagos** 3. Agrega o actualiza tu método de pago 4. Verifica que no haya pagos rechazados ```json Ejemplo de respuesta theme={null} { "error": "message_delivery_failed", "message": "Se requiere configurar método de pago", "code": "131042", "action_required": "Configurar pago en Meta Business", "instructions": "Ve a Meta Business Suite > Configuración > Pagos y agrega un método de pago válido" } ``` *** ### 130472 - Número Inválido **Causa**: El número de teléfono proporcionado no está registrado en WhatsApp. **Solución**: 1. Verifica que el número incluya código de país (ej: `+521` para México) 2. Confirma que el usuario tiene WhatsApp instalado 3. El número puede haber sido dado de baja de WhatsApp ```json Ejemplo de respuesta theme={null} { "error": "invalid_recipient", "message": "El número no está registrado en WhatsApp", "code": "130472" } ``` > [!NOTE] > > Algunos números corporativos o líneas fijas no pueden recibir mensajes de WhatsApp aunque tengan el formato correcto. *** ### 131026 - Usuario Bloqueado **Causa**: El destinatario ha bloqueado tu número de WhatsApp Business. **Solución**: * No hay solución técnica. El usuario debe desbloquear tu número manualmente. * Considera contactar al usuario por otro medio para resolver cualquier problema. ```json Ejemplo de respuesta theme={null} { "error": "user_blocked", "message": "El destinatario ha bloqueado este número", "code": "131026" } ``` *** ### 132000 - Template No Encontrado **Causa**: El template que intentas usar no existe, no está aprobado, o el nombre/idioma no coincide. **Solución**: 1. Verifica el nombre exacto del template en Meta Business Manager 2. Confirma que el template está **aprobado** (no en revisión o rechazado) 3. Verifica que el código de idioma sea correcto (`es_MX`, `en_US`, etc.) 4. Asegúrate de que el template pertenece a tu número de WhatsApp Business ```json Ejemplo de respuesta theme={null} { "error": "template_not_found", "message": "El template no existe o no está aprobado", "code": "132000", "details": { "template_name": "orden_confirmada", "language": "es_MX" } } ``` > [!TIP] > > Los templates pueden tardar hasta 24 horas en aprobarse. Si acabas de crear uno, espera a que Meta lo apruebe antes de usarlo. *** ### 131048 - Spam Rate Limit **Causa**: Demasiados mensajes de tu número han sido reportados como spam por los usuarios. **Solución**: 1. Reduce la frecuencia de envío de mensajes 2. Mejora la calidad y relevancia de tus mensajes 3. Asegúrate de que los usuarios hayan dado consentimiento para recibir mensajes 4. Espera 24-48 horas antes de intentar nuevamente ```json Ejemplo de respuesta theme={null} { "error": "spam_rate_limit", "message": "Tu cuenta ha excedido el límite de spam", "code": "131048", "action_required": "Reducir frecuencia de envío" } ``` > [!WARNING] > > Si continúas recibiendo este error, Meta puede restringir permanentemente tu cuenta de WhatsApp Business. *** ### 131052 - Media No Descargable **Causa**: Meta no pudo descargar el archivo multimedia desde la URL proporcionada. **Solución**: 1. Verifica que la URL sea pública y accesible 2. Asegúrate de que el servidor no bloquee requests de Meta 3. Para archivos privados o grandes, usa `POST /media/v1` para subirlos primero ```json Ejemplo de respuesta theme={null} { "error": "media_download_failed", "message": "No se pudo descargar el archivo multimedia", "code": "131052", "action_required": "Usar URL pública o subir via /media/v1" } ``` > [!NOTE] > > **Proxy Automático de Whaapy**: Si Meta no puede descargar tu archivo, Whaapy automáticamente intenta descargarlo y re-subirlo a Meta CDN. Sin embargo, esto tiene un límite de 5MB. Para archivos más grandes, usa `/media/v1`. *** ### 131051 - Tipo de Media No Soportado **Causa**: El formato del archivo no es compatible con WhatsApp. **Formatos soportados**: | Tipo | Formatos | | - | - | | Imagen | JPEG, PNG, WebP | | Video | MP4, 3GPP | | Audio | AAC, MP3, OGG, AMR | | Documento | PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT | | Sticker | WebP | *** ### 131053 - Archivo Demasiado Grande **Causa**: El archivo excede el tamaño máximo permitido. **Límites de tamaño**: | Tipo | Máximo | | - | - | | Imagen | 5 MB | | Video | 16 MB | | Audio | 16 MB | | Documento | 100 MB | | Sticker | 500 KB | *** ## Tabla Resumen de Errores | Código | Error | Causa | Solución Rápida | | - | - | - | - | | `131047` | Ventana Expirada | >24h sin respuesta del usuario | Enviar template | | `131042` | Pago Requerido | Sin método de pago en Meta | Configurar pago | | `130472` | Número Inválido | No tiene WhatsApp | Verificar número | | `131026` | Bloqueado | Usuario te bloqueó | Contactar por otro medio | | `132000` | Template No Existe | Nombre incorrecto o no aprobado | Verificar en Meta | | `131048` | Spam Rate Limit | Muchos reportes de spam | Reducir envíos | | `131052` | Media No Descargable | URL privada o bloqueada | Usar `/media/v1` | | `131051` | Formato No Soportado | Archivo incompatible | Convertir formato | | `131053` | Archivo Grande | Excede límite | Comprimir o usar `/media/v1` | *** ## Manejo de Errores en Código ```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', content: 'Hola!' }) }); const data = await response.json(); if (data.error) { switch (data.code) { case '131047': // Enviar template en su lugar await sendTemplate(data.data.conversationId); break; case '131052': // Re-subir media via /media/v1 const mediaId = await uploadMedia(mediaUrl); await sendWithMediaId(mediaId); break; default: console.error('Error:', data.message); } } else { console.log('Mensaje enviado:', data.data.id); } ``` ```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', 'content': 'Hola!' } ) data = response.json() if 'error' in data: if data.get('code') == '131047': # Enviar template en su lugar send_template(data['data']['conversationId']) elif data.get('code') == '131052': # Re-subir media via /media/v1 media_id = upload_media(media_url) send_with_media_id(media_id) else: print(f"Error: {data['message']}") else: print(f"Mensaje enviado: {data['data']['id']}") ``` *** ## Reintentar Mensajes Fallidos Cuando un mensaje falla, Whaapy lo guarda con `status: failed`. Una vez que las condiciones permitan su envío, puedes reintentarlo: ```bash theme={null} POST /messages/v1/{message_id}/retry ``` Ver [Reintentar Mensaje](/api-reference/messages/retry) para más detalles. # Mover Contacto Source: https://docs.whaapy.com/api-reference/funnels/contacts-move POST https://api.whaapy.com/funnel/v1/contacts/{id}/move Mueve un contacto a otra etapa del funnel > [!NOTE] > > **Scope requerido:** `funnels:write` Cambia la etapa de un contacto en el funnel. Puedes moverlo a cualquier etapa o quitarlo del funnel. > [!TIP] > > Envía `stage_id: null` para quitar al contacto de todas las etapas (útil para leads descartados). *** ## Path Parameters **id** `string` *required* UUID del contacto a mover *** ## Body Parameters **stage\_id** `string` *required* UUID de la etapa destino. Envía `null` para quitar el contacto de cualquier etapa. *** ## Ejemplos ### Mover a una etapa ```bash cURL theme={null} curl -X POST https://api.whaapy.com/funnel/v1/contacts/550e8400-e29b-41d4-a716-446655440000/move \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stage_id": "stage-qualified-uuid" }' ``` ```javascript Node.js theme={null} const contactId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/funnel/v1/contacts/${contactId}/move`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ stage_id: 'stage-qualified-uuid' }) } ); ``` ```python Python theme={null} import requests contact_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.post( f'https://api.whaapy.com/funnel/v1/contacts/{contact_id}/move', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'stage_id': 'stage-qualified-uuid' } ) ``` ```php PHP theme={null} $contactId = '550e8400-e29b-41d4-a716-446655440000'; $ch = curl_init("https://api.whaapy.com/funnel/v1/contacts/{$contactId}/move"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'stage_id' => 'stage-qualified-uuid' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` ### Quitar de todas las etapas ```json Request theme={null} { "stage_id": null } ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "contact_id": "550e8400-e29b-41d4-a716-446655440000", "previous_stage": { "id": "stage-lead-uuid", "name": "Lead" }, "new_stage": { "id": "stage-qualified-uuid", "name": "Qualified" }, "moved_at": "2026-01-28T12:00:00Z" } ``` ### Quitar de etapa (null) ```json 200 OK theme={null} { "contact_id": "550e8400-e29b-41d4-a716-446655440000", "previous_stage": { "id": "stage-lead-uuid", "name": "Lead" }, "new_stage": null, "moved_at": "2026-01-28T12:00:00Z" } ``` *** ## Errores ### Contacto no encontrado ```json 404 Not Found theme={null} { "error": "not_found", "message": "Contacto no encontrado" } ``` ### Etapa no encontrada ```json 404 Not Found theme={null} { "error": "not_found", "message": "Etapa no encontrada" } ``` *** ## Webhooks Cuando mueves un contacto, se dispara el webhook `contact.stage_changed`: ```json theme={null} { "event": "contact.stage_changed", "data": { "contact_id": "550e8400-e29b-41d4-a716-446655440000", "phone_number": "+5215512345678", "previous_stage_id": "stage-lead-uuid", "previous_stage_name": "Lead", "new_stage_id": "stage-qualified-uuid", "new_stage_name": "Qualified", "moved_at": "2026-01-28T12:00:00Z" } } ``` *** ## Movimiento Masivo Para mover múltiples contactos a la vez, usa la [API de operaciones masivas](/api-reference/contacts/bulk): ```json theme={null} POST /contacts/v1/bulk { "operation": "set_funnel_stage", "contact_ids": ["uuid-1", "uuid-2", "uuid-3"], "funnel_stage_id": "stage-qualified-uuid" } ``` *** ## Próximos Pasos > **[Listar Etapas](/api-reference/funnels/stages-list)** > > Ver etapas disponibles > **[Operaciones Masivas](/api-reference/contacts/bulk)** > > Mover múltiples contactos > **[Obtener Contacto](/api-reference/contacts/get)** > > Ver etapa actual del contacto > **[Webhooks](/api-reference/webhooks/events)** > > Recibir notificaciones de cambios # Funnels Source: https://docs.whaapy.com/api-reference/funnels/overview Gestiona las etapas de tu funnel de ventas y mueve contactos entre ellas ## ¿Qué es el Funnel de Whaapy? El funnel de Whaapy te permite organizar tus contactos en etapas de tu proceso de ventas. Cada negocio tiene un único funnel con múltiples etapas personalizables. ``` ┌─────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐ │ Lead │ → │ Qualified │ → │ Proposal │ → │ Closed │ │ (120) │ │ (45) │ │ (20) │ │ (30) │ └─────────┘ └───────────┘ └──────────┘ └─────────┘ ``` ## Conceptos Clave > **Etapas (Stages)** > > Cada etapa representa un paso en tu proceso de ventas. Las etapas tienen: > > * **Nombre**: Identificador visible (ej: "Lead", "Qualified") > * **Posición**: Orden en el funnel (0 = primera) > * **Color**: Color hex para visualización (#6366f1) > * **Contactos**: Número de contactos en esa etapa > **Movimiento de Contactos** > > Los contactos pueden moverse entre etapas: > > * Manualmente vía API > * Automáticamente vía webhooks de sistemas externos > * Un contacto solo puede estar en una etapa a la vez > * `stage_id: null` quita al contacto de todas las etapas > **Webhooks** > > Cada acción dispara webhooks para sincronización: > > * `funnel_stage.created` - Nueva etapa creada > * `funnel_stage.updated` - Etapa modificada > * `funnel_stage.deleted` - Etapa eliminada > * `contact.stage_changed` - Contacto movido de etapa ## Autenticación Todos los endpoints requieren autenticación con API Key: ```bash theme={null} Authorization: Bearer wha_TU_API_KEY ``` > [!NOTE] > > Obtén tu API Key en [Dashboard → Configuración → API](https://app.whaapy.com/settings/api) ## Scopes Requeridos | Endpoint | Scope | | - | - | | GET (listar, obtener) | `funnels:read` | | POST, PATCH, DELETE | `funnels:write` | ## Endpoints Disponibles > **[Listar Etapas](/api-reference/funnels/stages-list)** > > `GET /funnel/v1/stages` > **[Obtener Etapa](/api-reference/funnels/stages-get)** > > `GET /funnel/v1/stages/:id` > **[Crear Etapa](/api-reference/funnels/stages-create)** > > `POST /funnel/v1/stages` > **[Actualizar Etapa](/api-reference/funnels/stages-update)** > > `PATCH /funnel/v1/stages/:id` > **[Eliminar Etapa](/api-reference/funnels/stages-delete)** > > `DELETE /funnel/v1/stages/:id` > **[Reordenar Etapas](/api-reference/funnels/stages-reorder)** > > `PATCH /funnel/v1/stages/reorder` > **[Mover Contacto](/api-reference/funnels/contacts-move)** > > `POST /funnel/v1/contacts/:id/move` ## Casos de Uso Comunes ### Sincronización con CRM ```javascript theme={null} // Cuando un lead cambia de etapa en tu CRM, actualiza Whaapy await fetch('https://api.whaapy.com/funnel/v1/contacts/uuid/move', { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ stage_id: 'qualified-stage-uuid' }) }); ``` ### Automatización con n8n/Zapier 1. **Trigger**: Webhook `contact.stage_changed` de Whaapy 2. **Filter**: Si `new_stage.name === "Qualified"` 3. **Action**: Crear deal en tu CRM con datos del contacto ### Reportes de Pipeline ```javascript theme={null} // Obtener vista de pipeline con conteos const response = await fetch('https://api.whaapy.com/funnel/v1/stages', { headers: { 'Authorization': 'Bearer wha_xxx' } }); const { stages } = await response.json(); // stages = [ // { name: "Lead", contact_count: 120 }, // { name: "Qualified", contact_count: 45 }, // ... // ] ``` ## Límites y Consideraciones > [!WARNING] > > * **Máximo 50 etapas** por negocio > * **No puedes eliminar** una etapa con contactos (muévelos primero) > * **Reordenar** actualiza todas las posiciones en una transacción ## Próximos Pasos > **[Listar Etapas](/api-reference/funnels/stages-list)** > > Empieza listando las etapas existentes > **[Mover Contacto](/api-reference/funnels/contacts-move)** > > Aprende a mover contactos entre etapas # Crear Etapa Source: https://docs.whaapy.com/api-reference/funnels/stages-create POST https://api.whaapy.com/funnel/v1/stages Crea una nueva etapa en el funnel > [!NOTE] > > **Scope requerido:** `funnels:write` Agrega una nueva etapa a tu funnel. Por defecto se coloca al final. > [!TIP] > > Si no especificas `position`, la etapa se agregará automáticamente al final del funnel. *** ## Body Parameters **name** `string` *required* Nombre de la etapa (1-100 caracteres) **color** `string` (default: #6366f1) Color en formato hex. Ej: `#10b981` **position** `number` Posición en el funnel (0 = primera). Si no se proporciona, se asigna al final. *** ## Ejemplos ```bash cURL theme={null} curl -X POST https://api.whaapy.com/funnel/v1/stages \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Negotiation", "color": "#f59e0b" }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/funnel/v1/stages', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Negotiation', color: '#f59e0b' }) }); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/funnel/v1/stages', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'name': 'Negotiation', 'color': '#f59e0b' } ) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/funnel/v1/stages'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'name' => 'Negotiation', 'color' => '#f59e0b' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` *** ## Respuesta Exitosa ```json 201 Created theme={null} { "stage": { "id": "550e8400-e29b-41d4-a716-446655440003", "name": "Negotiation", "position": 3, "color": "#f59e0b", "contact_count": 0, "created_at": "2026-01-28T12:00:00Z", "updated_at": "2026-01-28T12:00:00Z" } } ``` *** ## Errores ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "Datos inválidos", "details": { "name": ["El nombre es requerido"], "color": ["Color inválido. Usar formato hex: #RRGGBB"] } } ``` *** ## Webhooks Cuando creas una etapa, se dispara el webhook `funnel_stage.created`: ```json theme={null} { "event": "funnel_stage.created", "data": { "stage_id": "550e8400-e29b-41d4-a716-446655440003", "name": "Negotiation", "position": 3, "color": "#f59e0b", "created_at": "2026-01-28T12:00:00Z" } } ``` *** ## Próximos Pasos > **[Listar Etapas](/api-reference/funnels/stages-list)** > > Ver todas las etapas > **[Mover Contacto](/api-reference/funnels/contacts-move)** > > Asignar contactos a la nueva etapa # Eliminar Etapa Source: https://docs.whaapy.com/api-reference/funnels/stages-delete DELETE https://api.whaapy.com/funnel/v1/stages/{id} Elimina una etapa del funnel > [!NOTE] > > **Scope requerido:** `funnels:write` Elimina una etapa del funnel. Solo es posible si no hay contactos en esa etapa. > [!WARNING] > > No puedes eliminar una etapa que tenga contactos. Primero debes mover los contactos a otra etapa usando [POST /funnel/v1/contacts/:id/move](/api-reference/funnels/contacts-move) o la [API de operaciones masivas](/api-reference/contacts/bulk). *** ## Path Parameters **id** `string` *required* UUID de la etapa a eliminar *** ## Ejemplos ```bash cURL theme={null} curl -X DELETE https://api.whaapy.com/funnel/v1/stages/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const stageId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/funnel/v1/stages/${stageId}`, { method: 'DELETE', headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } ); ``` ```python Python theme={null} import requests stage_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.delete( f'https://api.whaapy.com/funnel/v1/stages/{stage_id}', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) ``` ```php PHP theme={null} $stageId = '550e8400-e29b-41d4-a716-446655440000'; $ch = curl_init("https://api.whaapy.com/funnel/v1/stages/{$stageId}"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "success": true, "message": "Etapa eliminada", "stage_id": "550e8400-e29b-41d4-a716-446655440000" } ``` *** ## Errores ### Etapa no encontrada ```json 404 Not Found theme={null} { "error": "not_found", "message": "Etapa no encontrada" } ``` ### Etapa tiene contactos ```json 409 Conflict theme={null} { "error": "has_contacts", "message": "No se puede eliminar la etapa porque tiene 45 contacto(s). Mueve los contactos primero.", "contact_count": 45 } ``` > [!TIP] > > Para mover contactos masivamente antes de eliminar, usa: > > ```json theme={null} > POST /contacts/v1/bulk > { > "operation": "set_funnel_stage", > "contact_ids": ["uuid-1", "uuid-2", ...], > "funnel_stage_id": "otra-etapa-uuid" > } > ``` *** ## Webhooks Cuando eliminas una etapa, se dispara el webhook `funnel_stage.deleted`: ```json theme={null} { "event": "funnel_stage.deleted", "data": { "stage_id": "550e8400-e29b-41d4-a716-446655440000", "deleted_at": "2026-01-28T12:00:00Z" } } ``` *** ## Próximos Pasos > **[Listar Etapas](/api-reference/funnels/stages-list)** > > Ver etapas restantes > **[Crear Etapa](/api-reference/funnels/stages-create)** > > Agregar nueva etapa # Obtener Etapa Source: https://docs.whaapy.com/api-reference/funnels/stages-get GET https://api.whaapy.com/funnel/v1/stages/{id} Obtiene los detalles de una etapa específica > [!NOTE] > > **Scope requerido:** `funnels:read` Obtén información detallada de una etapa del funnel por su ID, incluyendo el conteo de contactos. *** ## Path Parameters **id** `string` *required* UUID de la etapa *** ## Ejemplos ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/funnel/v1/stages/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const stageId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/funnel/v1/stages/${stageId}`, { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } ); const data = await response.json(); ``` ```python Python theme={null} import requests stage_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.get( f'https://api.whaapy.com/funnel/v1/stages/{stage_id}', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) ``` ```php PHP theme={null} $stageId = '550e8400-e29b-41d4-a716-446655440000'; $ch = curl_init("https://api.whaapy.com/funnel/v1/stages/{$stageId}"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "stage": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Qualified", "position": 1, "color": "#10b981", "contact_count": 45, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T10:00:00Z" } } ``` *** ## Errores ### 400 Bad Request ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "ID de etapa inválido" } ``` ### 404 Not Found ```json 404 Not Found theme={null} { "error": "not_found", "message": "Etapa no encontrada" } ``` *** ## Próximos Pasos > **[Actualizar Etapa](/api-reference/funnels/stages-update)** > > Modificar nombre o color > **[Eliminar Etapa](/api-reference/funnels/stages-delete)** > > Eliminar etapa del funnel # Listar Etapas Source: https://docs.whaapy.com/api-reference/funnels/stages-list GET https://api.whaapy.com/funnel/v1/stages Obtiene todas las etapas del funnel con conteo de contactos > [!NOTE] > > **Scope requerido:** `funnels:read` Obtén todas las etapas de tu funnel ordenadas por posición, incluyendo el número de contactos en cada una. > [!NOTE] > > Las etapas siempre se devuelven ordenadas por `position` (ascendente). El `contact_count` se calcula dinámicamente. *** ## Ejemplos ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/funnel/v1/stages" \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/funnel/v1/stages', { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } }); const data = await response.json(); console.log(data.stages); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/funnel/v1/stages', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/funnel/v1/stages'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $data = json_decode($response, true); print_r($data['stages']); ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "stages": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lead", "position": 0, "color": "#6366f1", "contact_count": 120, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T10:00:00Z" }, { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Qualified", "position": 1, "color": "#10b981", "contact_count": 45, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T10:00:00Z" }, { "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Closed", "position": 2, "color": "#f59e0b", "contact_count": 30, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T10:00:00Z" } ], "total": 3 } ``` ### Campos de Respuesta | Campo | Tipo | Descripción | | - | - | - | | `stages` | array | Array de etapas ordenadas por posición | | `stages[].id` | string | UUID de la etapa | | `stages[].name` | string | Nombre de la etapa | | `stages[].position` | number | Posición en el funnel (0 = primera) | | `stages[].color` | string | Color en formato hex | | `stages[].contact_count` | number | Número de contactos en esta etapa | | `total` | number | Total de etapas | *** ## Errores ### 401 Unauthorized ```json 401 Unauthorized theme={null} { "error": "unauthorized", "message": "API key inválida o expirada" } ``` ### 403 Forbidden ```json 403 Forbidden theme={null} { "error": "forbidden", "message": "Scope 'funnels:read' requerido" } ``` *** ## Próximos Pasos > **[Crear Etapa](/api-reference/funnels/stages-create)** > > Agregar nueva etapa al funnel > **[Mover Contacto](/api-reference/funnels/contacts-move)** > > Mover contacto entre etapas > **[Reordenar](/api-reference/funnels/stages-reorder)** > > Cambiar orden de etapas > **[Listar Contactos](/api-reference/contacts/list)** > > Filtrar contactos por etapa # Reordenar Etapas Source: https://docs.whaapy.com/api-reference/funnels/stages-reorder PATCH https://api.whaapy.com/funnel/v1/stages/reorder Cambia el orden de las etapas del funnel > [!NOTE] > > **Scope requerido:** `funnels:write` Reorganiza las etapas de tu funnel en un nuevo orden. La operación es atómica: todas las posiciones se actualizan en una sola transacción. > [!TIP] > > Usa este endpoint cuando el usuario arrastre etapas en tu UI de Kanban. *** ## Body Parameters **stage\_ids** `string[]` *required* Array de UUIDs de etapas en el nuevo orden deseado. La primera etapa tendrá posición 0. > [!NOTE] > > Debes incluir todos los IDs de las etapas que quieres reordenar. Las etapas no incluidas mantendrán su posición relativa. *** ## Ejemplos ```bash cURL theme={null} curl -X PATCH https://api.whaapy.com/funnel/v1/stages/reorder \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stage_ids": [ "550e8400-e29b-41d4-a716-446655440002", "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001" ] }' ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/funnel/v1/stages/reorder', { method: 'PATCH', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ stage_ids: [ '550e8400-e29b-41d4-a716-446655440002', // Nueva posición 0 '550e8400-e29b-41d4-a716-446655440000', // Nueva posición 1 '550e8400-e29b-41d4-a716-446655440001' // Nueva posición 2 ] }) }); ``` ```python Python theme={null} import requests response = requests.patch( 'https://api.whaapy.com/funnel/v1/stages/reorder', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'stage_ids': [ '550e8400-e29b-41d4-a716-446655440002', '550e8400-e29b-41d4-a716-446655440000', '550e8400-e29b-41d4-a716-446655440001' ] } ) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/funnel/v1/stages/reorder'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'stage_ids' => [ '550e8400-e29b-41d4-a716-446655440002', '550e8400-e29b-41d4-a716-446655440000', '550e8400-e29b-41d4-a716-446655440001' ] ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "success": true, "stages": [ { "id": "550e8400-e29b-41d4-a716-446655440002", "name": "Closed", "position": 0, "color": "#f59e0b", "contact_count": 30, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T12:30:00Z" }, { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Lead", "position": 1, "color": "#6366f1", "contact_count": 120, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T12:30:00Z" }, { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Qualified", "position": 2, "color": "#10b981", "contact_count": 45, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T12:30:00Z" } ] } ``` *** ## Errores ### 400 Bad Request ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "Datos inválidos", "details": { "stage_ids": ["Debe proporcionar al menos una etapa"] } } ``` ### 404 Not Found ```json 404 Not Found theme={null} { "error": "not_found", "message": "Algunas etapas no existen", "invalid_ids": ["id-que-no-existe"] } ``` *** ## Webhooks Cuando reordenas etapas, se dispara el webhook `funnel_stages.reordered`: ```json theme={null} { "event": "funnel_stages.reordered", "data": { "stage_ids": [ "550e8400-e29b-41d4-a716-446655440002", "550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001" ], "reordered_at": "2026-01-28T12:30:00Z" } } ``` *** ## Próximos Pasos > **[Listar Etapas](/api-reference/funnels/stages-list)** > > Ver nuevo orden > **[Actualizar Etapa](/api-reference/funnels/stages-update)** > > Modificar etapa individual # Actualizar Etapa Source: https://docs.whaapy.com/api-reference/funnels/stages-update PATCH https://api.whaapy.com/funnel/v1/stages/{id} Actualiza el nombre o color de una etapa > [!NOTE] > > **Scope requerido:** `funnels:write` Modifica las propiedades de una etapa existente. Solo envía los campos que quieres modificar. *** ## Path Parameters **id** `string` *required* UUID de la etapa a actualizar *** ## Body Parameters **name** `string` Nuevo nombre (1-100 caracteres) **color** `string` Nuevo color en formato hex > [!NOTE] > > Envía solo los campos que quieres modificar. *** ## Ejemplos ```bash cURL theme={null} curl -X PATCH https://api.whaapy.com/funnel/v1/stages/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Hot Lead", "color": "#ef4444" }' ``` ```javascript Node.js theme={null} const stageId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch( `https://api.whaapy.com/funnel/v1/stages/${stageId}`, { method: 'PATCH', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Hot Lead', color: '#ef4444' }) } ); ``` ```python Python theme={null} import requests stage_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.patch( f'https://api.whaapy.com/funnel/v1/stages/{stage_id}', headers={ 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, json={ 'name': 'Hot Lead', 'color': '#ef4444' } ) ``` ```php PHP theme={null} $stageId = '550e8400-e29b-41d4-a716-446655440000'; $ch = curl_init("https://api.whaapy.com/funnel/v1/stages/{$stageId}"); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH'); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'name' => 'Hot Lead', 'color' => '#ef4444' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "stage": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Hot Lead", "position": 0, "color": "#ef4444", "contact_count": 120, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-28T12:30:00Z" } } ``` *** ## Errores ### 400 Bad Request ```json 400 Bad Request theme={null} { "error": "validation_error", "message": "Datos inválidos", "details": { "color": ["Color inválido. Usar formato hex: #RRGGBB"] } } ``` ### 404 Not Found ```json 404 Not Found theme={null} { "error": "not_found", "message": "Etapa no encontrada" } ``` *** ## Webhooks Cuando actualizas una etapa, se dispara el webhook `funnel_stage.updated`: ```json theme={null} { "event": "funnel_stage.updated", "data": { "stage_id": "550e8400-e29b-41d4-a716-446655440000", "name": "Hot Lead", "color": "#ef4444", "updated_at": "2026-01-28T12:30:00Z" } } ``` *** ## Próximos Pasos > **[Reordenar Etapas](/api-reference/funnels/stages-reorder)** > > Cambiar posición de etapas > **[Eliminar Etapa](/api-reference/funnels/stages-delete)** > > Eliminar etapa # Subir Media Source: https://docs.whaapy.com/api-reference/media/upload-media POST https://api.whaapy.com/media/v1 Sube archivos a Meta CDN y obtén un media_id para usar en mensajes Sube imágenes, videos, audios o documentos directamente a Meta CDN. ## Casos de Uso * **Archivos grandes**: Videos o documentos que excedan los límites del proxy automático * **Reutilización**: Enviar el mismo archivo a múltiples destinatarios sin re-subirlo * **Control**: Subir archivos de forma asíncrona antes de enviar mensajes > [!NOTE] > > El `media_id` expira después de aproximadamente 30 días en Meta CDN. *** ## Headers Requeridos | Header | Valor | | - | - | | `Authorization` | `Bearer wha_TU_API_KEY` | | `Content-Type` | `multipart/form-data` | > [!WARNING] > > No uses `application/json`. Este endpoint requiere `multipart/form-data` porque envía archivos binarios. *** ## Parámetros del Body **file** `binary` *required* El archivo binario a subir. Debe enviarse como multipart/form-data. **type** `string` *required* Tipo de media: `image`, `video`, `audio`, `document`, `sticker` *** ## Límites de Tamaño | Tipo | Formatos | Tamaño Máximo | | - | - | - | | `image` | JPEG, PNG, WebP | 5 MB | | `video` | MP4, 3GPP | 16 MB | | `audio` | AAC, MP3, OGG, AMR | 16 MB | | `document` | PDF, DOC, XLS, PPT, TXT | 100 MB | | `sticker` | WebP | 500 KB | *** ## Ejemplo con cURL ```bash cURL theme={null} curl -X POST https://api.whaapy.com/media/v1 \ -H "Authorization: Bearer wha_TU_API_KEY" \ -F "file=@/ruta/a/imagen.jpg" \ -F "type=image" ``` ```javascript Node.js theme={null} const FormData = require('form-data'); const fs = require('fs'); const formData = new FormData(); formData.append('file', fs.createReadStream('./imagen.jpg')); formData.append('type', 'image'); const response = await fetch('https://api.whaapy.com/media/v1', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY' }, body: formData }); const data = await response.json(); console.log('Media ID:', data.media_id); ``` ```python Python theme={null} import requests with open('imagen.jpg', 'rb') as f: response = requests.post( 'https://api.whaapy.com/media/v1', headers={'Authorization': 'Bearer wha_TU_API_KEY'}, files={'file': f}, data={'type': 'image'} ) print('Media ID:', response.json()['media_id']) ``` *** ## Respuesta Exitosa ```json 200 OK theme={null} { "media_id": "123456789012345", "mime_type": "image/jpeg", "file_size": 1048576, "type": "image" } ``` *** ## Flujo Completo ### Paso 1: Subir el archivo ```bash theme={null} curl -X POST https://api.whaapy.com/media/v1 \ -H "Authorization: Bearer wha_TU_API_KEY" \ -F "file=@video.mp4" \ -F "type=video" ``` Respuesta: ```json theme={null} { "media_id": "123456789012345", "mime_type": "video/mp4", "file_size": 15728640, "type": "video" } ``` ### Paso 2: Enviar mensaje con el media\_id ```bash 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": "video", "video": { "id": "123456789012345", "caption": "Mira este video" } }' ``` > [!TIP] > > Puedes usar el mismo `media_id` para enviar el archivo a múltiples destinatarios. El ID es válido por 30 días. *** ## Cuándo usar este endpoint | Escenario | Recomendación | | - | - | | Imagen pequeña con URL pública | Usa `image.link` directamente | | Video de cualquier tamaño | Usa `/media/v1` | | Documento grande | Usa `/media/v1` | | Enviar a múltiples personas | Usa `/media/v1` y reutiliza el `media_id` | | URL privada o protegida | Usa `/media/v1` | > [!NOTE] > > Para imágenes que excedan 5MB por URL, Whaapy las comprime automáticamente. No necesitas usar este endpoint solo por el tamaño. *** ## Errores Comunes ### Tipo de archivo no permitido ```json theme={null} { "error": "Tipo de archivo no permitido", "message": "Para video, los formatos permitidos son: video/mp4, video/3gpp" } ``` ### Archivo demasiado grande ```json theme={null} { "error": "Archivo demasiado grande", "message": "El tamaño máximo para video es 16MB" } ``` ### WhatsApp no configurado ```json theme={null} { "error": "WhatsApp no configurado", "message": "No se encontró configuración de WhatsApp activa para este negocio" } ``` *** ## Próximos Pasos > **[Enviar Media](/api-reference/messages/send-media)** > > Usa el media\_id para enviar imágenes, videos o documentos > **[Enviar Mensajes](/api-reference/messages/send)** > > Envía mensajes de texto simples # Reintentar Mensaje Source: https://docs.whaapy.com/api-reference/messages/retry POST https://api.whaapy.com/messages/v1/{id}/retry Reintenta el envío de un mensaje que falló Cuando un mensaje falla (por ejemplo, por ventana de 24h expirada), queda guardado en Whaapy con `status: failed`. Este endpoint te permite reintentar el envío una vez que las condiciones permitan su entrega (por ejemplo, después de que el usuario te escriba o después de enviar un template). ## Path Parameters **id** `string` *required* UUID del mensaje a reintentar. Lo recibes en `data.id` de la respuesta original. ## Cuándo Usar Este Endpoint > **Después de que el usuario responda** > > Si enviaste un mensaje y falló por ventana de 24h (`131047`), el usuario puede escribirte para reabrir la ventana. Luego puedes reintentar el mensaje original. > **Después de enviar un template** > > Los templates pueden enviarse en cualquier momento. Una vez que el usuario responde al template, se abre una nueva ventana de 24h y puedes reintentar mensajes pendientes. > **Después de configurar pagos** > > Si el error fue `131042` (pago requerido), una vez configures el método de pago en Meta Business, puedes reintentar. ## Request ```bash cURL theme={null} curl -X POST https://api.whaapy.com/messages/v1/550e8400-e29b-41d4-a716-446655440000/retry \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript JavaScript theme={null} const messageId = '550e8400-e29b-41d4-a716-446655440000'; const response = await fetch(`https://api.whaapy.com/messages/v1/${messageId}/retry`, { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } }); const data = await response.json(); ``` ```python Python theme={null} import requests message_id = '550e8400-e29b-41d4-a716-446655440000' response = requests.post( f'https://api.whaapy.com/messages/v1/{message_id}/retry', headers={ 'Authorization': 'Bearer wha_TU_API_KEY' } ) print(response.json()) ``` ## Respuesta Exitosa ```json 200 OK theme={null} { "message": "Mensaje reenviado exitosamente", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "messageId": "wamid.HBgLNTIxNTUxMjM0NTY3OA==", "status": "sent", "retriedAt": "2026-01-22T11:00:00Z" } } ``` ## Respuestas de Error ### Mensaje No Encontrado ```json 404 Not Found theme={null} { "error": "not_found", "message": "Mensaje no encontrado" } ``` ### Mensaje No Está Fallido ```json 400 Bad Request theme={null} { "error": "invalid_state", "message": "Solo se pueden reintentar mensajes con status 'failed'" } ``` ### Reintento También Falló ```json 400 Bad Request theme={null} { "error": "retry_failed", "message": "La ventana de conversación de 24 horas sigue expirada", "code": "131047", "action_required": "Enviar template o esperar respuesta del usuario" } ``` ## Flujo de Recuperación de Mensajes Fallidos ```mermaid theme={null} sequenceDiagram participant App as Tu App participant Whaapy as Whaapy API participant Meta as Meta/WhatsApp participant User as Usuario App->>Whaapy: POST /messages/v1 (texto) Whaapy->>Meta: Enviar mensaje Meta-->>Whaapy: Error 131047 (ventana cerrada) Whaapy-->>App: { status: "failed", id: "xxx" } Note over App,User: Opción 1: Enviar template App->>Whaapy: POST /messages/v1 (template) Whaapy->>Meta: Enviar template Meta-->>User: Template recibido User-->>Meta: Responde al template Meta-->>Whaapy: Mensaje del usuario Note over App: Ventana reabierta App->>Whaapy: POST /messages/v1/xxx/retry Whaapy->>Meta: Reenviar mensaje original Meta-->>User: Mensaje entregado Whaapy-->>App: { status: "sent" } ``` ## Best Practices > [!TIP] > > **Implementa un sistema de cola** para mensajes fallidos. Cuando recibas un webhook indicando que el usuario respondió, procesa la cola de mensajes pendientes para esa conversación. > [!WARNING] > > **No reintentes inmediatamente** después de un error de ventana. Primero debes lograr que el usuario responda (via template u otro medio). ## Campos del Mensaje Reusados Cuando reintentas un mensaje, Whaapy usa los mismos datos que guardó originalmente: * **Destinatario**: El mismo `to` o `conversationId` * **Tipo**: El mismo `type` (text, image, etc.) * **Contenido**: El mismo `content`, `text`, `image`, etc. * **Metadata**: Se preservan campos opcionales como `caption` > [!NOTE] > > Si necesitas cambiar el contenido del mensaje, mejor envía uno nuevo en lugar de reintentar. # Enviar Mensaje de Texto Source: https://docs.whaapy.com/api-reference/messages/send POST https://api.whaapy.com/messages/v1 Envía mensajes de texto simple a WhatsApp El endpoint principal para enviar mensajes. Esta página cubre mensajes de texto. Para otros tipos, consulta: > **[Media](/api-reference/messages/send-media)** > > Imágenes, videos, audio, documentos, stickers > **[Templates](/api-reference/messages/send-templates)** > > Templates pre-aprobados por Meta > **[Interactive](/api-reference/messages/send-interactive)** > > Botones y listas interactivas > **[Ubicación](/api-reference/messages/send-location)** > > Coordenadas GPS y direcciones *** ## Formato Híbrido Whaapy acepta **dos estilos** de request, dándote flexibilidad: **Estilo Whaapy (Simplificado)** Formato minimalista para casos comunes: ```json theme={null} { "to": "+5215512345678", "content": "¡Hola! Gracias por contactarnos." } ``` **Estilo Meta (Compatible)** 100% compatible con WhatsApp Cloud API: ```json theme={null} { "messaging_product": "whatsapp", "to": "+5215512345678", "type": "text", "text": { "body": "¡Hola! Gracias por contactarnos.", "preview_url": true } } ``` > [!TIP] > > El estilo Whaapy es más corto y fácil de usar. El estilo Meta es útil si ya tienes código funcionando con la API oficial de WhatsApp. *** ## Campos de Destino Debes proporcionar **al menos uno** de estos campos para identificar el destinatario: **to** `string` Número de teléfono con código de país. Ejemplo: `+5215512345678` **phone\_number** `string` Alias de `to`. Mismo formato. **conversationId** `string` UUID de una conversación existente en Whaapy. Útil para responder en conversaciones activas. > [!NOTE] > > Si proporcionas `conversationId`, Whaapy obtiene automáticamente el número de teléfono de esa conversación. *** ## Campos de Contenido (Texto) **content** `string` **Estilo Whaapy**. El texto del mensaje a enviar. **text** `object` **Estilo Meta**. Objeto con las siguientes propiedades: * `body` (string, requerido): El texto del mensaje * `preview_url` (boolean, opcional): Si mostrar vista previa de URLs *** ## Ejemplos ### Texto Simple (Estilo Whaapy) ```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", "content": "¡Hola! Gracias por tu mensaje. Te responderemos pronto." }' ``` ```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', content: '¡Hola! Gracias por tu mensaje. Te responderemos pronto.' }) }); const data = await response.json(); console.log(data); ``` ```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', 'content': '¡Hola! Gracias por tu mensaje. Te responderemos pronto.' } ) print(response.json()) ``` ```php PHP theme={null} $ch = curl_init('https://api.whaapy.com/messages/v1'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'to' => '+5215512345678', 'content' => '¡Hola! Gracias por tu mensaje. Te responderemos pronto.' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); echo $response; ``` ### Texto con Vista Previa de URL (Estilo Meta) ```json Request theme={null} { "to": "+5215512345678", "type": "text", "text": { "body": "Mira nuestro catálogo: https://whaapy.com/productos", "preview_url": true } } ``` > [!NOTE] > > Con `preview_url: true`, WhatsApp mostrará una vista previa enriquecida del enlace (imagen, título, descripción) si la URL lo soporta. ### Responder a Conversación Existente ```json Request theme={null} { "conversationId": "550e8400-e29b-41d4-a716-446655440000", "content": "Gracias por tu paciencia. Ya tenemos tu pedido listo." } ``` *** ## Respuesta Exitosa ```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", "content": "¡Hola! Gracias por tu mensaje.", "messageType": "text", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` ### Campos de Respuesta | Campo | Descripción | | - | - | | `messaging_product` | Siempre `"whatsapp"` | | `contacts` | Array con info del contacto (`wa_id` es el ID de WhatsApp) | | `messages` | Array con IDs del mensaje (`id` de Whaapy, `wamid` de Meta) | | `data.id` | UUID del mensaje en Whaapy (usar para retry) | | `data.conversationId` | UUID de la conversación | | `data.status` | Estado: `sent`, `delivered`, `read`, `failed` | *** ## Errores Comunes ### Ventana de 24 Horas Expirada ```json Error 131047 theme={null} { "error": "message_delivery_failed", "message": "La ventana de conversación de 24 horas ha expirado", "code": "131047", "action_required": "Usar template message", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "failed" } } ``` > [!WARNING] > > WhatsApp solo permite enviar mensajes de texto libre dentro de las 24 horas posteriores al último mensaje del usuario. Fuera de esta ventana, debes usar un [template message](/api-reference/messages/send-templates). Ver [Códigos de Error](/api-reference/errors) para la lista completa de errores. *** ## Control de IA (Opcional) Puedes controlar el comportamiento del agente IA al enviar mensajes usando el campo `ai`: **ai.pause** `boolean` Pausar la IA después de enviar este mensaje **ai.pauseDuration** `number` Duración de la pausa en minutos (default: 5, máximo: 1440) **ai.disable** `boolean` Desactivar la IA permanentemente en esta conversación ### Ejemplo: Enviar y Pausar IA ```json theme={null} { "to": "+5215512345678", "content": "Te atiendo personalmente", "ai": { "pause": true, "pauseDuration": 30 } } ``` > [!NOTE] > > Esto es útil cuando integras con sistemas externos (n8n, Zapier, CRMs) y necesitas evitar que la IA responda automáticamente. Ver [Agente IA](/api-reference/agent/overview) para más opciones. *** ## Próximos Pasos > **[Enviar Media](/api-reference/messages/send-media)** > > Imágenes, videos, audio, documentos > **[Templates](/api-reference/messages/send-templates)** > > Mensajes fuera de ventana de 24h > **[Reintentar Mensaje](/api-reference/messages/retry)** > > Reenviar mensajes fallidos > **[Subir Media](/api-reference/media/upload-media)** > > Subir archivos grandes # Enviar Contactos Source: https://docs.whaapy.com/api-reference/messages/send-contacts POST https://api.whaapy.com/messages/v1 Envía tarjetas de contacto (vCard) por WhatsApp Comparte información de contacto como tarjetas vCard. El usuario podrá guardar el contacto directamente en su agenda. > [!NOTE] > > Los contactos solo pueden enviarse dentro de la ventana de 24 horas. Fuera de la ventana, usa [templates](/api-reference/messages/send-templates). *** ## Ejemplo Básico ```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": "contacts", "contacts": [ { "name": { "formatted_name": "Juan Pérez", "first_name": "Juan", "last_name": "Pérez" }, "phones": [ { "phone": "+5215598765432", "type": "WORK" } ] } ] }' ``` ```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: 'contacts', contacts: [ { name: { formatted_name: 'Juan Pérez', first_name: 'Juan', last_name: 'Pérez' }, phones: [ { phone: '+5215598765432', type: 'WORK' } ] } ] }) }); ``` ```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': 'contacts', 'contacts': [ { 'name': { 'formatted_name': 'Juan Pérez', 'first_name': 'Juan', 'last_name': 'Pérez' }, 'phones': [ { 'phone': '+5215598765432', 'type': 'WORK' } ] } ] } ) ``` *** ## Contacto Completo ```json Request theme={null} { "to": "+5215512345678", "type": "contacts", "contacts": [ { "name": { "formatted_name": "Juan Pérez García", "first_name": "Juan", "last_name": "Pérez", "middle_name": "García", "prefix": "Lic.", "suffix": "Jr." }, "phones": [ { "phone": "+5215598765432", "type": "WORK", "wa_id": "5215598765432" }, { "phone": "+5215512345678", "type": "CELL" } ], "emails": [ { "email": "juan.perez@empresa.com", "type": "WORK" }, { "email": "juanp@gmail.com", "type": "HOME" } ], "addresses": [ { "street": "Av. Reforma 123", "city": "Ciudad de México", "state": "CDMX", "zip": "06600", "country": "México", "country_code": "MX", "type": "WORK" } ], "org": { "company": "Whaapy Inc.", "department": "Ventas", "title": "Director de Ventas" }, "urls": [ { "url": "https://whaapy.com", "type": "WORK" }, { "url": "https://linkedin.com/in/juanperez", "type": "WORK" } ], "birthday": "1990-05-15" } ] } ``` *** ## Múltiples Contactos Puedes enviar varios contactos en un solo mensaje: ```json Request theme={null} { "to": "+5215512345678", "type": "contacts", "contacts": [ { "name": { "formatted_name": "Soporte Técnico", "first_name": "Soporte" }, "phones": [ { "phone": "+5215500001111", "type": "WORK" } ] }, { "name": { "formatted_name": "Ventas", "first_name": "Ventas" }, "phones": [ { "phone": "+5215500002222", "type": "WORK" } ] }, { "name": { "formatted_name": "Facturación", "first_name": "Facturación" }, "phones": [ { "phone": "+5215500003333", "type": "WORK" } ] } ] } ``` *** ## Campos ### Objeto Contact **contacts** `array` *required* Array de objetos de contacto. Puedes enviar múltiples contactos. ### Objeto Name (requerido) **name.formatted\_name** `string` *required* Nombre completo formateado. Este es el campo principal que ve el usuario. **name.first\_name** `string` Nombre de pila. **name.last\_name** `string` Apellido. **name.middle\_name** `string` Segundo nombre o apellido materno. **name.prefix** `string` Prefijo (Dr., Lic., Ing., etc.). **name.suffix** `string` Sufijo (Jr., Sr., III, etc.). ### Objeto Phone **phones\[].phone** `string` *required* Número de teléfono con código de país. **phones\[].type** `string` Tipo: `CELL`, `MAIN`, `IPHONE`, `HOME`, `WORK`. **phones\[].wa\_id** `string` ID de WhatsApp (solo si el número tiene WhatsApp). ### Objeto Email **emails\[].email** `string` *required* Dirección de correo electrónico. **emails\[].type** `string` Tipo: `HOME`, `WORK`. ### Objeto Address **addresses\[].street** `string` Calle y número. **addresses\[].city** `string` Ciudad. **addresses\[].state** `string` Estado o provincia. **addresses\[].zip** `string` Código postal. **addresses\[].country** `string` País. **addresses\[].country\_code** `string` Código ISO del país (MX, US, ES, etc.). **addresses\[].type** `string` Tipo: `HOME`, `WORK`. ### Objeto Organization **org.company** `string` Nombre de la empresa. **org.department** `string` Departamento. **org.title** `string` Cargo o título. ### Objeto URL **urls\[].url** `string` *required* URL del sitio web o perfil. **urls\[].type** `string` Tipo: `HOME`, `WORK`. ### Otros Campos **birthday** `string` Fecha de nacimiento en formato `YYYY-MM-DD`. *** ## Respuesta Exitosa ```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": "contacts", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` *** ## Casos de Uso > **Compartir datos del asesor** > > ```json theme={null} > { > "contacts": [{ > "name": { "formatted_name": "María López - Tu Asesora" }, > "phones": [{ "phone": "+5215512345678", "type": "WORK", "wa_id": "5215512345678" }], > "emails": [{ "email": "maria.lopez@empresa.com", "type": "WORK" }], > "org": { "company": "Empresa SA", "title": "Asesora de Ventas" } > }] > } > ``` > **Directorio de departamentos** > > ```json theme={null} > { > "contacts": [ > { > "name": { "formatted_name": "Soporte Técnico" }, > "phones": [{ "phone": "+5215500001111", "type": "WORK" }], > "emails": [{ "email": "soporte@empresa.com", "type": "WORK" }] > }, > { > "name": { "formatted_name": "Ventas" }, > "phones": [{ "phone": "+5215500002222", "type": "WORK" }], > "emails": [{ "email": "ventas@empresa.com", "type": "WORK" }] > } > ] > } > ``` > **Tarjeta de negocio** > > ```json theme={null} > { > "contacts": [{ > "name": { > "formatted_name": "Restaurante La Parrilla", > "first_name": "La Parrilla" > }, > "phones": [ > { "phone": "+5215512345678", "type": "WORK" }, > { "phone": "+5215598765432", "type": "CELL" } > ], > "addresses": [{ > "street": "Av. Insurgentes 456", > "city": "CDMX", > "country": "México", > "type": "WORK" > }], > "urls": [{ "url": "https://laparrilla.mx", "type": "WORK" }] > }] > } > ``` *** ## Próximos Pasos > **[Enviar Reacciones](/api-reference/messages/send-reactions)** > > Reacciona a mensajes con emojis > **[Reintentar Mensaje](/api-reference/messages/retry)** > > Reenviar mensajes fallidos # Mensajes Interactivos Source: https://docs.whaapy.com/api-reference/messages/send-interactive POST https://api.whaapy.com/messages/v1 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). *** ## Botones (Reply Buttons) Muestra hasta **3 botones** con opciones rápidas. Ideal para preguntas simples o menús pequeños. ### Ejemplo Básico ```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'}} ] } } } ) ``` ### Con Header y Footer ```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" } } ] } } } ``` ### Con Header de Imagen ```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" } } ] } } } ``` ### Campos de Botones **interactive.type** `string` *required* Debe ser `"button"` para reply buttons. **interactive.header** `object` Header opcional. Puede ser `text`, `image`, `video`, o `document`. **interactive.body.text** `string` *required* Texto principal del mensaje. Máximo 1024 caracteres. **interactive.footer.text** `string` Texto secundario en gris. Máximo 60 caracteres. **interactive.action.buttons** `array` *required* Array de 1-3 botones. Cada botón tiene `type: "reply"` y objeto `reply` con `id` y `title`. **button.reply.id** `string` *required* ID único del botón. Lo recibirás en el webhook cuando el usuario responda. Máximo 256 caracteres. **button.reply.title** `string` *required* Texto visible del botón. Máximo 20 caracteres. *** ## 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 ```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" } ] } ] } } } ``` ### Con Header y Footer ```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" } ] } ] } } } ``` ### Campos de Listas **interactive.type** `string` *required* Debe ser `"list"` para list messages. **interactive.header** `object` Header opcional. Solo soporta `type: "text"` en listas. **interactive.body.text** `string` *required* Texto principal. Máximo 1024 caracteres. **interactive.footer.text** `string` Texto secundario. Máximo 60 caracteres. **interactive.action.button** `string` *required* Texto del botón que abre la lista. Máximo 20 caracteres. **interactive.action.sections** `array` *required* Array de secciones. Máximo 10 secciones, máximo 10 rows en total. ### Campos de Section **section.title** `string` Título de la sección. Máximo 24 caracteres. Requerido si hay más de una sección. **section.rows** `array` *required* Array de opciones dentro de la sección. ### Campos de Row **row\.id** `string` *required* ID único de la opción. Lo recibirás en el webhook. Máximo 200 caracteres. **row\.title** `string` *required* Título visible de la opción. Máximo 24 caracteres. **row\.description** `string` Descripción adicional. Máximo 72 caracteres. *** ## 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 ```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' } } } } ) ``` ### Con Header, Footer e Imagen ```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" } } } } ``` ```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" } } } } ``` ### Campos de CTA URL **interactive.type** `string` *required* Debe ser `"cta_url"`. **interactive.header** `object` Header opcional. Puede ser `text`, `image`, `video`, o `document`. **interactive.body.text** `string` *required* Texto principal del mensaje. Máximo 1024 caracteres. **interactive.footer.text** `string` Texto secundario en gris. Máximo 60 caracteres. **interactive.action.name** `string` *required* Debe ser `"cta_url"`. **interactive.action.parameters.display\_text** `string` *required* Texto visible del botón. Máximo 30 caracteres. **interactive.action.parameters.url** `string` *required* URL que se abrirá al hacer tap en el botón. Debe ser una URL válida con `https://`. > [!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. *** ## 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. *** ## Respuesta Exitosa ```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" } } ``` *** ## 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 > **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" } } > ] > } > } > ``` > **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" } > ] > } > ] > } > } > ``` > **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" } } > ] > } > } > ``` > **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" > } > } > } > ``` > **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" > } > } > } > ``` *** ## 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 ```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" } } } } } } ``` ### Campos de Flow **interactive.type** `string` *required* Debe ser `"flow"`. **interactive.action.name** `string` *required* Debe ser `"flow"`. **interactive.action.parameters.flow\_message\_version** `string` *required* Actualmente debe ser `"3"`. **interactive.action.parameters.flow\_id** `string` ID único del Flow en Meta. Debes enviar `flow_id` o `flow_name`, pero no ambos. **interactive.action.parameters.flow\_name** `string` Nombre del Flow en Meta. Alternativa a `flow_id`. **interactive.action.parameters.flow\_cta** `string` *required* Texto del CTA visible para abrir el Flow. Recomendado: 30 caracteres o menos. **interactive.action.parameters.flow\_token** `string` Token propio de tu negocio para correlacionar la respuesta del Flow. **interactive.action.parameters.flow\_action** `string` `navigate` o `data_exchange`. Si no lo envías, Meta usa `navigate`. **interactive.action.parameters.flow\_action\_payload.screen** `string` Pantalla inicial del Flow cuando usas `navigate`. **interactive.action.parameters.flow\_action\_payload.data** `object` Datos iniciales que quieres inyectar a la primera pantalla del Flow. *** ## Próximos Pasos > **[Enviar Ubicación](/api-reference/messages/send-location)** > > Comparte coordenadas GPS > **[Enviar Contactos](/api-reference/messages/send-contacts)** > > Comparte tarjetas vCard # Enviar Ubicación Source: https://docs.whaapy.com/api-reference/messages/send-location POST https://api.whaapy.com/messages/v1 Envía ubicaciones y coordenadas GPS por WhatsApp Comparte ubicaciones con coordenadas GPS, nombre del lugar y dirección. El usuario verá un mapa interactivo que puede abrir en su app de mapas preferida. > [!NOTE] > > Las ubicaciones solo pueden enviarse dentro de la ventana de 24 horas. Fuera de la ventana, usa [templates](/api-reference/messages/send-templates). *** ## Ejemplo Básico ```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": "location", "location": { "latitude": 19.4326, "longitude": -99.1332, "name": "Oficina Central Whaapy", "address": "Av. Paseo de la Reforma 123, Col. Centro, CDMX" } }' ``` ```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: 'location', location: { latitude: 19.4326, longitude: -99.1332, name: 'Oficina Central Whaapy', address: 'Av. Paseo de la Reforma 123, Col. Centro, CDMX' } }) }); ``` ```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': 'location', 'location': { 'latitude': 19.4326, 'longitude': -99.1332, 'name': 'Oficina Central Whaapy', 'address': 'Av. Paseo de la Reforma 123, Col. Centro, CDMX' } } ) ``` *** ## Solo Coordenadas Si solo tienes las coordenadas, puedes omitir `name` y `address`: ```json Request theme={null} { "to": "+5215512345678", "type": "location", "location": { "latitude": 19.4326, "longitude": -99.1332 } } ``` > [!TIP] > > Siempre incluye `name` y `address` para una mejor experiencia de usuario. Sin estos campos, el usuario solo verá un pin en el mapa. *** ## Campos **location.latitude** `number` *required* Latitud de la ubicación. Valor entre -90 y 90. **location.longitude** `number` *required* Longitud de la ubicación. Valor entre -180 y 180. **location.name** `string` Nombre del lugar (ej: "Oficina Central", "Restaurante La Parrilla"). Máximo 1000 caracteres. **location.address** `string` Dirección completa. Máximo 1000 caracteres. *** ## Respuesta Exitosa ```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": "location", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` *** ## Casos de Uso > **Ubicación de tienda** > > ```json theme={null} > { > "to": "+5215512345678", > "type": "location", > "location": { > "latitude": 19.4326, > "longitude": -99.1332, > "name": "Whaapy Store - Sucursal Centro", > "address": "Av. Reforma 123, Piso 5, Col. Centro, CDMX, CP 06600" > } > } > ``` > **Punto de entrega** > > ```json theme={null} > { > "to": "+5215512345678", > "type": "location", > "location": { > "latitude": 19.4126, > "longitude": -99.1632, > "name": "Punto de Entrega - Pedido #12345", > "address": "Tu pedido será entregado aquí" > } > } > ``` > **Ubicación del repartidor** > > ```json theme={null} > { > "to": "+5215512345678", > "type": "location", > "location": { > "latitude": 19.4226, > "longitude": -99.1432, > "name": "Tu repartidor está aquí", > "address": "A 5 minutos de tu ubicación" > } > } > ``` *** ## Obtener Coordenadas Puedes obtener coordenadas de varias formas: 1. **Google Maps**: Click derecho en el mapa → "¿Qué hay aquí?" → Copia las coordenadas 2. **API de Geocoding**: Convierte direcciones a coordenadas con Google Maps API, Mapbox, etc. 3. **GPS del dispositivo**: Si tu app tiene acceso a la ubicación del usuario ### Ejemplo con Google Geocoding API ```javascript theme={null} // Convertir dirección a coordenadas async function getCoordinates(address) { const response = await fetch( `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=TU_API_KEY` ); const data = await response.json(); if (data.results[0]) { const { lat, lng } = data.results[0].geometry.location; return { latitude: lat, longitude: lng }; } throw new Error('Dirección no encontrada'); } ``` *** ## Próximos Pasos > **[Enviar Contactos](/api-reference/messages/send-contacts)** > > Comparte tarjetas vCard > **[Enviar Reacciones](/api-reference/messages/send-reactions)** > > Reacciona a mensajes con emojis # Enviar Media Source: https://docs.whaapy.com/api-reference/messages/send-media POST https://api.whaapy.com/messages/v1 Envía imágenes, videos, audio, documentos y stickers por WhatsApp Envía archivos multimedia a través de WhatsApp. Soporta imágenes, videos, audio, documentos y stickers. ## Métodos de Envío Hay dos formas de enviar media: **Con URL (link)** Proporciona una URL pública y Meta descargará el archivo: ```json theme={null} { "to": "+5215512345678", "type": "image", "image": { "link": "https://example.com/imagen.jpg", "caption": "Descripción opcional" } } ``` **Con media\_id** Primero sube el archivo con `/media/v1`, luego usa el ID: ```json theme={null} { "to": "+5215512345678", "type": "image", "image": { "id": "123456789012345", "caption": "Descripción opcional" } } ``` ### ¿Cuándo usar cada método? | Método | Tamaño | Mejor para | | - | - | - | | `link` | \< 5MB | URLs públicas de CDN, imágenes pequeñas | | `id` (via `/media/v1`) | Cualquiera | Archivos grandes, URLs privadas, múltiples envíos | > [!NOTE] > > **Compresión Automática de Imágenes**: Si envías una imagen por URL que excede 5MB, Whaapy automáticamente la detecta, la descarga, la comprime con calidad progresiva, y la sube a Meta. No necesitas hacer nada adicional - el proceso es transparente. > [!TIP] > > **Proxy Automático**: Si Meta no puede descargar tu URL (error 131052), Whaapy automáticamente descarga el archivo y lo re-sube. Para archivos muy grandes que no se pueden comprimir, usa `autoConvert: true` para enviarlos como documento, o súbelos primero via [/media/v1](/api-reference/media/upload-media). *** ## Imagen Formatos soportados: **JPEG, PNG, WebP**\ Tamaño máximo: **5 MB** ### Con URL ```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": "image", "image": { "link": "https://example.com/producto.jpg", "caption": "¡Mira nuestro nuevo producto!" } }' ``` ```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: 'image', image: { link: 'https://example.com/producto.jpg', caption: '¡Mira nuestro nuevo producto!' } }) }); ``` ```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': 'image', 'image': { 'link': 'https://example.com/producto.jpg', 'caption': '¡Mira nuestro nuevo producto!' } } ) ``` ### Con media\_id ```json theme={null} { "to": "+5215512345678", "type": "image", "image": { "id": "123456789012345", "caption": "Imagen subida previamente" } } ``` ### Campos **image.link** `string` URL pública de la imagen. Mutuamente excluyente con `id`. **image.id** `string` ID de media obtenido de `/media/v1`. Mutuamente excluyente con `link`. **image.caption** `string` Descripción opcional que aparece debajo de la imagen. Máximo 1024 caracteres. *** ## Video Formatos soportados: **MP4, 3GPP**\ Tamaño máximo: **16 MB**\ Códecs recomendados: H.264 video, AAC audio > [!WARNING] > > Para videos grandes (>5MB), siempre usa [/media/v1](/api-reference/media/upload-media) primero. El proxy automático tiene límites de tamaño. ### Con URL ```json Request theme={null} { "to": "+5215512345678", "type": "video", "video": { "link": "https://example.com/tutorial.mp4", "caption": "Tutorial de instalación" } } ``` ### Con media\_id (Recomendado) ```json Request theme={null} { "to": "+5215512345678", "type": "video", "video": { "id": "123456789012345", "caption": "Video subido previamente" } } ``` ### Campos **video.link** `string` URL pública del video. Mutuamente excluyente con `id`. **video.id** `string` ID de media obtenido de `/media/v1`. Mutuamente excluyente con `link`. **video.caption** `string` Descripción opcional. Máximo 1024 caracteres. *** ## Audio Formatos soportados: **AAC, MP3, OGG (Opus), AMR**\ Tamaño máximo: **16 MB** > [!NOTE] > > Los audios se muestran como notas de voz en WhatsApp si son formato OGG con códec Opus. ### Ejemplo ```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": "audio", "audio": { "link": "https://example.com/mensaje.mp3" } }' ``` ```json Request Body theme={null} { "to": "+5215512345678", "type": "audio", "audio": { "link": "https://example.com/mensaje.mp3" } } ``` ### Con media\_id ```json theme={null} { "to": "+5215512345678", "type": "audio", "audio": { "id": "123456789012345" } } ``` ### Campos **audio.link** `string` URL pública del audio. **audio.id** `string` ID de media obtenido de `/media/v1`. > [!TIP] > > Los audios no soportan `caption`. Si necesitas agregar contexto, envía un mensaje de texto antes o después. *** ## Documento Formatos soportados: **PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT**\ Tamaño máximo: **100 MB** ### Ejemplo ```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": "document", "document": { "link": "https://example.com/factura.pdf", "filename": "factura-enero-2026.pdf", "caption": "Tu factura del mes de enero" } }' ``` ```json Request Body theme={null} { "to": "+5215512345678", "type": "document", "document": { "link": "https://example.com/factura.pdf", "filename": "factura-enero-2026.pdf", "caption": "Tu factura del mes de enero" } } ``` ### Con media\_id ```json theme={null} { "to": "+5215512345678", "type": "document", "document": { "id": "123456789012345", "filename": "contrato.pdf", "caption": "Contrato firmado" } } ``` ### Campos **document.link** `string` URL pública del documento. **document.id** `string` ID de media obtenido de `/media/v1`. **document.filename** `string` *required* Nombre del archivo que verá el usuario al descargar. **Recomendado** para mejor UX. **document.caption** `string` Descripción opcional del documento. *** ## Sticker Formato soportado: **WebP**\ Tamaño máximo: **500 KB**\ Dimensiones: **512x512 píxeles** (recomendado) > [!NOTE] > > Los stickers animados deben usar WebP animado y no exceder 500KB. ### Ejemplo ```json Request theme={null} { "to": "+5215512345678", "type": "sticker", "sticker": { "link": "https://example.com/sticker.webp" } } ``` ### Con media\_id ```json theme={null} { "to": "+5215512345678", "type": "sticker", "sticker": { "id": "123456789012345" } } ``` ### Campos **sticker.link** `string` URL pública del sticker en formato WebP. **sticker.id** `string` ID de media obtenido de `/media/v1`. > [!WARNING] > > Los stickers no soportan `caption`. Son elementos visuales independientes. *** ## Respuesta Exitosa ```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": "image", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` *** ## Límites de Tamaño | Tipo | Formatos | Tamaño Máximo | | - | - | - | | `image` | JPEG, PNG, WebP | 5 MB | | `video` | MP4, 3GPP | 16 MB | | `audio` | AAC, MP3, OGG, AMR | 16 MB | | `document` | PDF, DOC, XLS, PPT, TXT | 100 MB | | `sticker` | WebP | 500 KB | *** ## Errores Comunes ### Media No Descargable (131052) ```json theme={null} { "error": "media_download_failed", "message": "No se pudo descargar el archivo multimedia", "code": "131052" } ``` **Solución**: Usa una URL pública o sube el archivo via [/media/v1](/api-reference/media/upload-media). ### Tipo de Media No Soportado (131051) ```json theme={null} { "error": "unsupported_media_type", "message": "El formato del archivo no es compatible", "code": "131051" } ``` **Solución**: Convierte el archivo a un formato soportado. ### Archivo Demasiado Grande (131053) ```json theme={null} { "error": "file_too_large", "message": "El archivo excede el tamaño máximo permitido", "code": "131053" } ``` **Solución**: Comprime el archivo o divídelo en partes más pequeñas. *** ## Flujo Recomendado para Archivos Grandes ```mermaid theme={null} sequenceDiagram participant App as Tu App participant Whaapy as Whaapy API participant Meta as Meta CDN App->>Whaapy: POST /media/v1 (archivo) Whaapy->>Meta: Upload a CDN Meta-->>Whaapy: media_id Whaapy-->>App: { media_id: "123..." } App->>Whaapy: POST /messages/v1 (con media_id) Whaapy->>Meta: Enviar mensaje con media_id Meta-->>Whaapy: Mensaje enviado Whaapy-->>App: { status: "sent" } ``` > [!TIP] > > El `media_id` expira después de \~30 días. Puedes reutilizarlo para enviar el mismo archivo a múltiples destinatarios sin re-subirlo. *** ## Conversión Automática (autoConvert) Cuando un archivo excede el límite de tamaño para su tipo, puedes usar `autoConvert: true` para que Whaapy intente comprimir automáticamente y, si la compresión no es suficiente, envíe el archivo como documento. **autoConvert** `boolean` (default: false) Habilita compresión automática y conversión a documento como fallback ### Comportamiento | Tipo | Límite | Con autoConvert | | - | - | - | | `image` | 5 MB | Comprime con calidad progresiva (85% → 70% → 50%). Si sigue excediendo, envía como documento | | `video` | 16 MB | **No comprime** (muy costoso). Si excede, envía como documento directamente | | `audio` | 16 MB | Comprime a MP3 64kbps mono. Si sigue excediendo, envía como documento | | `document` | 100 MB | Sin cambios (ya tiene el límite más alto) | ### Ejemplo: Imagen Grande con autoConvert ```json theme={null} { "to": "+5215512345678", "type": "image", "image": { "link": "https://example.com/foto-alta-resolucion.jpg", "caption": "Foto en alta resolución" }, "autoConvert": true } ``` **Sin `autoConvert`**: Si la imagen excede 5MB, recibirás error 413. **Con `autoConvert: true`**: 1. Whaapy intenta comprimir la imagen 2. Si la compresión es exitosa → se envía como imagen 3. Si la compresión no es suficiente → se envía como documento ### Respuesta cuando se convierte a documento ```json theme={null} { "messaging_product": "whatsapp", "messages": [{ "id": "..." }], "data": { "messageType": "document", "metadata": { "originalType": "image", "wasConverted": true, "reason": "Exceeded size limit after compression" } } } ``` > [!WARNING] > > Cuando un archivo se envía como documento, el destinatario no verá una vista previa en la conversación. Deberá descargarlo para verlo. > [!TIP] > > Para mejor experiencia de usuario, comprime tus imágenes antes de enviarlas. Si usas Cloudinary o servicios similares, agrega transformaciones como `q_auto,w_1920` para reducir el tamaño automáticamente. *** ## Error de Tamaño (media\_too\_large) Cuando un archivo excede el límite y `autoConvert` está deshabilitado: ```json theme={null} { "error": "media_too_large", "message": "image exceeds 5MB limit (7.9MB)", "suggestion": "Use autoConvert:true to auto-compress, or send as document type", "limits": { "image": "5MB", "video": "16MB", "audio": "16MB", "document": "100MB" } } ``` > [!NOTE] > > El error incluye los límites actuales de Meta WhatsApp Cloud API para tu referencia. *** ## Control de IA (Opcional) Puedes controlar el comportamiento del agente IA al enviar mensajes de media usando el campo `ai`: **ai.pause** `boolean` Pausar la IA después de enviar este mensaje **ai.pauseDuration** `number` Duración de la pausa en minutos (default: 5, máximo: 1440) **ai.disable** `boolean` Desactivar la IA permanentemente en esta conversación ### Ejemplo: Enviar Imagen y Pausar IA ```json theme={null} { "to": "+5215512345678", "type": "image", "image": { "link": "https://example.com/producto.jpg", "caption": "Aquí está la foto que pediste" }, "ai": { "pause": true, "pauseDuration": 30 } } ``` > [!NOTE] > > Esto es útil cuando integras con sistemas externos (n8n, Zapier, CRMs) y necesitas evitar que la IA responda automáticamente. Ver [Agente IA](/api-reference/agent/overview) para más opciones. *** ## Próximos Pasos > **[Subir Media](/api-reference/media/upload-media)** > > Endpoint para subir archivos grandes > **[Enviar Templates](/api-reference/messages/send-templates)** > > Mensajes fuera de ventana 24h > **[Agente IA](/api-reference/agent/overview)** > > Controla cuándo la IA responde # Enviar Reacciones Source: https://docs.whaapy.com/api-reference/messages/send-reactions POST https://api.whaapy.com/messages/v1 Reacciona a mensajes de WhatsApp con emojis Agrega reacciones emoji a mensajes existentes. Las reacciones son una forma rápida de responder sin enviar un mensaje nuevo. > [!NOTE] > > Las reacciones solo pueden enviarse dentro de la ventana de 24 horas y requieren el `message_id` del mensaje a reaccionar. *** ## Agregar Reacción ```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": "reaction", "reaction": { "message_id": "wamid.HBgLNTIxNTUxMjM0NTY3OA==", "emoji": "👍" } }' ``` ```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: 'reaction', reaction: { message_id: 'wamid.HBgLNTIxNTUxMjM0NTY3OA==', emoji: '👍' } }) }); ``` ```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': 'reaction', 'reaction': { 'message_id': 'wamid.HBgLNTIxNTUxMjM0NTY3OA==', 'emoji': '👍' } } ) ``` *** ## Quitar Reacción Para quitar una reacción, envía el mismo request con `emoji` vacío: ```json Request theme={null} { "to": "+5215512345678", "type": "reaction", "reaction": { "message_id": "wamid.HBgLNTIxNTUxMjM0NTY3OA==", "emoji": "" } } ``` > [!TIP] > > Solo puedes tener **una reacción activa** por mensaje. Enviar una nueva reacción reemplaza la anterior. *** ## Campos **reaction.message\_id** `string` *required* ID del mensaje a reaccionar. Puede ser el `wamid` de Meta o el `id` de Whaapy. **reaction.emoji** `string` *required* Emoji de la reacción. String vacío `""` para quitar la reacción. *** ## Emojis Comunes para Reacciones | Emoji | Uso Típico | | - | - | | 👍 | Aprobación, confirmación | | ❤️ | Agradecimiento, cariño | | 😂 | Algo gracioso | | 😮 | Sorpresa | | 😢 | Empatía, disculpa | | 🙏 | Gracias, por favor | | ✅ | Tarea completada | | 🎉 | Celebración | > [!NOTE] > > Puedes usar cualquier emoji Unicode válido. WhatsApp soporta la mayoría de emojis estándar. *** ## Respuesta Exitosa ```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": "reaction", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` *** ## Obtener el message\_id El `message_id` necesario para reaccionar lo obtienes de: ### 1. Respuesta de envío Cuando envías un mensaje, la respuesta incluye el `wamid`: ```json theme={null} { "messages": [ { "id": "uuid-whaapy", "wamid": "wamid.HBgLNTIxNTUx..." } ] } ``` ### 2. Webhook de mensaje recibido Cuando recibes un mensaje del usuario: ```json theme={null} { "messages": [ { "id": "wamid.HBgLNTIxNTUx...", "from": "5215512345678", "text": { "body": "Hola!" } } ] } ``` ### 3. Base de datos de Whaapy Si guardaste el mensaje, puedes usar el UUID de Whaapy directamente. *** ## Casos de Uso > **Confirmar recepción de pedido** > > ```javascript theme={null} > // Cuando el usuario envía su pedido, reacciona con ✅ > await sendReaction(messageId, '✅'); > > // Luego envía mensaje de confirmación > await sendMessage(to, 'Pedido recibido. Te confirmamos en breve.'); > ``` > **Agradecer un mensaje positivo** > > ```javascript theme={null} > // Detectar mensaje positivo y reaccionar > if (sentiment === 'positive') { > await sendReaction(messageId, '❤️'); > } > ``` > **Marcar mensaje como leído** > > ```javascript theme={null} > // Usar reacción como indicador de "visto" > await sendReaction(messageId, '👀'); > ``` *** ## Limitaciones * Solo puedes reaccionar a mensajes de los últimos 30 días * Solo puedes tener una reacción por mensaje (la última reemplaza la anterior) * Las reacciones requieren ventana de 24 horas activa * No puedes reaccionar a mensajes de canales o grupos (solo chats 1:1) *** ## Flujo de Reacciones ```mermaid theme={null} sequenceDiagram participant User as Usuario participant App as Tu App participant Whaapy as Whaapy API User->>Whaapy: "Me encanta su producto!" Whaapy->>App: Webhook (message_id: wamid.xxx) Note over App: Detectar sentimiento positivo App->>Whaapy: POST /messages/v1 (reaction: ❤️) Whaapy->>User: Reacción ❤️ en el mensaje App->>Whaapy: POST /messages/v1 (text) Whaapy->>User: "¡Gracias! Nos alegra mucho 🙌" ``` *** ## Próximos Pasos > **[Enviar Texto](/api-reference/messages/send)** > > Mensajes de texto simple > **[Enviar Media](/api-reference/messages/send-media)** > > Imágenes, videos, audio # Enviar Templates Source: https://docs.whaapy.com/api-reference/messages/send-templates POST https://api.whaapy.com/messages/v1 Envía mensajes template pre-aprobados por Meta Los **templates** son mensajes pre-aprobados por Meta que puedes enviar en cualquier momento, incluso fuera de la ventana de 24 horas. Son ideales para: * Notificaciones transaccionales (confirmación de pedido, envío, etc.) * Re-engagement con clientes inactivos * Marketing autorizado * Alertas y recordatorios > [!WARNING] > > Los templates deben ser aprobados por Meta antes de usarlos. La aprobación puede tomar hasta 24 horas. *** ## Formatos de Envío Whaapy soporta **tres formas** de enviar templates: **Shortcut Whaapy** Formato simplificado con `templateName` y `template_parameters`: ```json theme={null} { "to": "+5215512345678", "type": "template", "templateName": "orden_confirmada", "template_parameters": ["Juan", "#12345"] } ``` **Shortcut con Header** Incluye imagen/video/documento en el header con `header_media`: ```json theme={null} { "to": "+5215512345678", "type": "template", "templateName": "promo_imagen", "template_parameters": ["20%", "Enero 2026"], "header_media": { "type": "image", "url": "https://example.com/promo.jpg" } } ``` **Formato Meta Completo** Control total con `template.components`: ```json theme={null} { "to": "+5215512345678", "type": "template", "template": { "name": "orden_confirmada", "language": { "code": "es_MX" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan" }, { "type": "text", "text": "#12345" } ] } ] } } ``` *** ## Shortcut Whaapy (Simplificado) El formato más sencillo para templates con solo parámetros de texto en el body. ```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": "template", "templateName": "orden_confirmada", "template_parameters": ["Juan Pérez", "#ORD-12345", "$1,500.00"] }' ``` ```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: 'template', templateName: 'orden_confirmada', template_parameters: ['Juan Pérez', '#ORD-12345', '$1,500.00'] }) }); ``` ```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': 'template', 'templateName': 'orden_confirmada', 'template_parameters': ['Juan Pérez', '#ORD-12345', '$1,500.00'] } ) ``` ### Campos **templateName** `string` *required* Nombre exacto del template como aparece en Meta Business Manager. **template\_parameters** `array` Array de strings con los valores para cada `{{placeholder}}` del template body, en orden. > [!NOTE] > > El shortcut asume idioma `es_MX` por defecto. Para otros idiomas, usa el formato Meta completo. *** ## Shortcut con Header Media Para templates que incluyen imagen, video o documento en el header. ```json Request theme={null} { "to": "+5215512345678", "type": "template", "templateName": "promo_navidad", "template_parameters": ["30%", "25 de diciembre"], "header_media": { "type": "image", "url": "https://example.com/navidad-promo.jpg" } } ``` ### Con Video ```json theme={null} { "to": "+5215512345678", "type": "template", "templateName": "tutorial_producto", "template_parameters": ["Producto X"], "header_media": { "type": "video", "url": "https://example.com/tutorial.mp4" } } ``` ### Con Documento ```json theme={null} { "to": "+5215512345678", "type": "template", "templateName": "factura_mensual", "template_parameters": ["Enero 2026", "$2,500.00"], "header_media": { "type": "document", "url": "https://example.com/factura.pdf" } } ``` ### Campos de header\_media **header\_media.type** `string` *required* Tipo de media: `image`, `video`, o `document`. **header\_media.url** `string` *required* URL pública del archivo multimedia. **header\_media.media\_id** `string` Media ID de Meta previamente subido con el mismo número/WABA que enviará el template. ### Ejemplo con `media_id` (sin URL pública) ```json theme={null} { "to": "+5215512345678", "type": "template", "templateName": "orden_servicio", "template_parameters": ["Juan"], "header_media": { "type": "document", "media_id": "155857201882704" } } ``` > [!NOTE] > > Compatibilidad legacy: si un cliente envía accidentalmente el `media_id` en `header_media.url`, Whaapy intenta normalizarlo automáticamente. *** ## Formato Meta Completo Control total sobre todos los componentes del template: header, body, footer y botones. ### Template Básico ```json Request theme={null} { "to": "+5215512345678", "type": "template", "template": { "name": "orden_confirmada", "language": { "policy": "deterministic", "code": "es_MX" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan Pérez" }, { "type": "text", "text": "#ORD-12345" } ] } ] } } ``` ### Template con Header de Imagen ```json Request theme={null} { "to": "+5215512345678", "type": "template", "template": { "name": "confirmacion_pago", "language": { "policy": "deterministic", "code": "es_MX" }, "components": [ { "type": "header", "parameters": [ { "type": "image", "image": { "link": "https://example.com/recibo.jpg" } } ] }, { "type": "body", "parameters": [ { "type": "text", "text": "Juan Pérez" }, { "type": "text", "text": "#ORD-12345" } ] } ] } } ``` ### Template con Currency y Date ```json Request theme={null} { "to": "+5215512345678", "type": "template", "template": { "name": "resumen_compra", "language": { "policy": "deterministic", "code": "es_MX" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan Pérez" }, { "type": "text", "text": "#ORD-12345" }, { "type": "currency", "currency": { "fallback_value": "$1,500.00 MXN", "code": "MXN", "amount_1000": 1500000 } }, { "type": "date_time", "date_time": { "fallback_value": "25 de enero, 2026" } } ] } ] } } ``` ### Template con Botones ```json Request theme={null} { "to": "+5215512345678", "type": "template", "template": { "name": "confirmacion_envio", "language": { "policy": "deterministic", "code": "es_MX" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan" }, { "type": "text", "text": "#ENV-789" } ] }, { "type": "button", "sub_type": "quick_reply", "index": "0", "parameters": [ { "type": "payload", "payload": "confirmar_recibido_789" } ] }, { "type": "button", "sub_type": "url", "index": "1", "parameters": [ { "type": "text", "text": "ENV-789" } ] } ] } } ``` *** ## Tipos de Parámetros | Tipo | Uso | Ejemplo | | - | - | - | | `text` | Texto simple | `{ "type": "text", "text": "Juan Pérez" }` | | `currency` | Valores monetarios formateados | Ver ejemplo abajo | | `date_time` | Fechas y horas | Ver ejemplo abajo | | `image` | Imagen en header | `{ "type": "image", "image": { "link": "url" } }` | | `video` | Video en header | `{ "type": "video", "video": { "link": "url" } }` | | `document` | PDF en header | `{ "type": "document", "document": { "link": "url" } }` | | `payload` | Datos de callback para botones | `{ "type": "payload", "payload": "data" }` | ### Parámetro Currency ```json theme={null} { "type": "currency", "currency": { "fallback_value": "$1,500.00 MXN", "code": "MXN", "amount_1000": 1500000 } } ``` > [!NOTE] > > `amount_1000` es el monto multiplicado por 1000. Para \$1,500.00 MXN, el valor es 1500000. ### Parámetro Date/Time ```json theme={null} { "type": "date_time", "date_time": { "fallback_value": "25 de enero, 2026 a las 3:00 PM" } } ``` *** ## Estructura de Botones Los botones en templates tienen diferentes tipos: | sub\_type | Descripción | Parámetros | | - | - | - | | `quick_reply` | Botón de respuesta rápida | `payload` (string de callback) | | `url` | Botón que abre URL | `text` (sufijo dinámico de URL) | | `catalog` | Abre catálogo de productos | N/A | ### Política de IDs (`payload`) para quick reply Cuando envías templates por `POST /messages/v1`, Whaapy resuelve el `payload` del botón en este orden: 1. `override` explícito en request **solo si** `allowButtonIdOverride=true` 2. `buttonId` configurado en el template del negocio 3. fallback automático: `btn_{index}_{templateName}` Esto garantiza trazabilidad estable en webhooks por negocio. **allowButtonIdOverride** `boolean` Opcional (default: `false`). Si está en `true`, permite sobreescribir el payload de quick reply desde `template.components`. ### Ejemplo de override explícito (avanzado) ```json theme={null} { "to": "+5215512345678", "type": "template", "templateName": "confirmacion_envio", "template_parameters": ["Juan", "#ENV-789"], "allowButtonIdOverride": true, "template": { "components": [ { "type": "button", "sub_type": "quick_reply", "index": "0", "parameters": [ { "type": "payload", "payload": "confirmar_envio_custom" } ] } ] } } ``` ### Ejemplo Quick Reply ```json theme={null} { "type": "button", "sub_type": "quick_reply", "index": "0", "parameters": [ { "type": "payload", "payload": "confirmar_pedido_12345" } ] } ``` ### Ejemplo URL Dinámica Si tu template tiene botón URL con `https://example.com/track/{{1}}`: ```json theme={null} { "type": "button", "sub_type": "url", "index": "0", "parameters": [ { "type": "text", "text": "ENV-789" } ] } ``` El resultado será `https://example.com/track/ENV-789`. **button.index** `string` *required* Índice del botón (0-9). Corresponde al orden de los botones en el template. *** ## Respuesta Exitosa ```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": "template", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` *** ## Errores Comunes ### Template No Encontrado (132000) ```json theme={null} { "error": "template_not_found", "message": "El template no existe o no está aprobado", "code": "132000", "details": { "template_name": "orden_confirmada", "language": "es_MX" } } ``` **Soluciones**: 1. Verifica el nombre exacto en Meta Business Manager 2. Confirma que el template está aprobado (no en revisión) 3. Verifica el código de idioma correcto ### Número de Parámetros Incorrecto ```json theme={null} { "error": "invalid_template_parameters", "message": "El template requiere 3 parámetros, se recibieron 2" } ``` **Solución**: Revisa cuántos `{{placeholders}}` tiene tu template y envía la misma cantidad de parámetros. ### Permisos de Media (`MEDIA_PERMISSION_DENIED`) ```json theme={null} { "error": "Media permission denied", "message": "No se pudo acceder al media_id del header con las credenciales actuales de WhatsApp.", "code": "MEDIA_PERMISSION_DENIED", "hint": "Verifica que el media fue subido con el mismo número/WABA/token que envía el template." } ``` **Soluciones**: 1. Confirma que el `media_id` pertenece al mismo WABA/número emisor. 2. Re-sube el archivo usando la misma credencial que envía el template. 3. Si no tienes `media_id` válido, usa `header_media.url` pública. *** ## Códigos de Idioma Comunes | Código | Idioma | | - | - | | `es_MX` | Español (México) | | `es_ES` | Español (España) | | `es_AR` | Español (Argentina) | | `en_US` | Inglés (Estados Unidos) | | `pt_BR` | Portugués (Brasil) | > [!TIP] > > Usa `"policy": "deterministic"` para asegurar que se use exactamente el idioma especificado. Sin esto, WhatsApp puede seleccionar un idioma diferente basado en las preferencias del usuario. *** ## Crear Templates en Meta 1. Ve a [Meta Business Suite](https://business.facebook.com) 2. Navega a **WhatsApp Manager** → **Message Templates** 3. Click en **Create Template** 4. Selecciona categoría (Marketing, Utility, Authentication) 5. Define header, body, footer y botones 6. Envía para aprobación > [!NOTE] > > Los templates de categoría "Authentication" tienen reglas especiales y mayores restricciones. *** ## Próximos Pasos > **[Mensajes Interactivos](/api-reference/messages/send-interactive)** > > Botones y listas sin templates > **[Reintentar Mensajes](/api-reference/messages/retry)** > > Reenviar después de template # Resumen de API Source: https://docs.whaapy.com/api-reference/overview Visión general de la API de Whaapy # Resumen de API La API de Whaapy te permite enviar mensajes de WhatsApp de forma programática. Está diseñada para ser **compatible con Meta WhatsApp Cloud API** mientras ofrece shortcuts que simplifican casos de uso comunes. *** ## Base URL ``` https://api.whaapy.com ``` Todas las rutas documentadas son relativas a esta URL base. *** ## Autenticación Todas las requests requieren un header `Authorization` con tu API Key: ```http theme={null} Authorization: Bearer wha_TU_API_KEY Content-Type: application/json ``` Ver [Autenticación](/api-reference/authentication) para más detalles. *** ## Formato Híbrido Nuestra API acepta **dos estilos** de request, dándote flexibilidad para usar el formato que prefieras: **Estilo Whaapy** Formato simplificado para casos comunes: ```json theme={null} { "to": "+5215512345678", "content": "Hola!" } ``` **Estilo Meta** 100% compatible con WhatsApp Cloud API: ```json theme={null} { "messaging_product": "whatsapp", "to": "+5215512345678", "type": "text", "text": { "body": "Hola!" } } ``` > [!TIP] > > El estilo Whaapy es más corto y fácil de usar. El estilo Meta es útil si ya tienes código funcionando con la API oficial de WhatsApp. *** ## Endpoints Disponibles ### Mensajes | Método | Endpoint | Descripción | Documentación | | - | - | - | - | | `POST` | `/messages/v1` | Envía cualquier tipo de mensaje | [Ver docs](/api-reference/messages/send) | | `POST` | `/messages/v1/:id/retry` | Reintenta un mensaje fallido | [Ver docs](/api-reference/messages/retry) | ### Media | Método | Endpoint | Descripción | Documentación | | - | - | - | - | | `POST` | `/media/v1` | Sube archivos a Meta CDN | [Ver docs](/api-reference/media/upload-media) | *** ## Tipos de Mensaje Soportados > **[text](/api-reference/messages/send)** > > Texto simple > **[image](/api-reference/messages/send-media)** > > JPEG, PNG, WebP > **[video](/api-reference/messages/send-media)** > > MP4 hasta 16MB > **[audio](/api-reference/messages/send-media)** > > MP3, OGG, AAC > **[document](/api-reference/messages/send-media)** > > PDF, Word, Excel > **[sticker](/api-reference/messages/send-media)** > > WebP hasta 500KB > **[template](/api-reference/messages/send-templates)** > > Pre-aprobados por Meta > **[interactive](/api-reference/messages/send-interactive)** > > Botones y listas > **[location](/api-reference/messages/send-location)** > > Coordenadas GPS > **[contacts](/api-reference/messages/send-contacts)** > > Tarjetas vCard > **[reaction](/api-reference/messages/send-reactions)** > > Emojis en mensajes *** ## Códigos de Respuesta HTTP | Código | Significado | Descripción | | - | - | - | | `200` | OK | Request exitoso | | `400` | Bad Request | Error de validación en el request | | `401` | Unauthorized | API Key inválida o faltante | | `403` | Forbidden | Sin permisos para este scope | | `404` | Not Found | Recurso no encontrado | | `429` | Too Many Requests | Rate limit excedido | | `500` | Internal Server Error | Error interno del servidor | Ver [Códigos de Error](/api-reference/errors) para errores específicos de WhatsApp. *** ## Rate Limits | Tipo | Límite | | - | - | | Mensajes por minuto | 1,000 | | Requests por minuto | 100 | > [!NOTE] > > Los rate limits pueden variar según tu plan. Contacta soporte para límites personalizados. *** ## Límites de Tamaño de Media | Tipo | Formatos | Tamaño Máximo | | - | - | - | | `image` | JPEG, PNG, WebP | 5 MB | | `video` | MP4, 3GPP | 16 MB | | `audio` | AAC, MP3, OGG, AMR | 16 MB | | `document` | PDF, DOC, XLS, PPT, TXT | 100 MB | | `sticker` | WebP | 500 KB | *** ## Ventana de 24 Horas > [!WARNING] > > WhatsApp tiene una regla de **ventana de 24 horas**: solo puedes enviar mensajes de texto libre a usuarios que te han escrito en las últimas 24 horas. **Dentro de la ventana** (24h desde último mensaje del usuario): * Puedes enviar cualquier tipo de mensaje * Texto, imágenes, videos, interactivos, etc. **Fuera de la ventana** (>24h sin respuesta): * Solo puedes enviar **template messages** pre-aprobados por Meta * Ver [Enviar Templates](/api-reference/messages/send-templates) *** ## Estructura de Respuesta Exitosa Todas las respuestas exitosas siguen esta estructura: ```json theme={null} { "messaging_product": "whatsapp", "contacts": [ { "input": "+5215512345678", "wa_id": "5215512345678" } ], "messages": [ { "id": "uuid-whaapy", "wamid": "wamid.HBgLNTIxNTUx..." } ], "data": { "id": "uuid-del-mensaje", "conversationId": "uuid-de-conversacion", "messageType": "text", "direction": "outbound", "status": "sent", "createdAt": "2026-01-22T10:30:00Z" } } ``` | Campo | Descripción | | - | - | | `messaging_product` | Siempre `"whatsapp"` | | `contacts[].wa_id` | ID de WhatsApp del destinatario | | `messages[].id` | UUID del mensaje en Whaapy | | `messages[].wamid` | ID del mensaje en Meta | | `data.conversationId` | UUID de la conversación | | `data.status` | `sent`, `delivered`, `read`, o `failed` | *** ## Estructura de Respuesta de Error ```json theme={null} { "error": "message_delivery_failed", "message": "Descripción legible del error", "code": "131047", "action_required": "Acción sugerida", "instructions": "Pasos detallados para resolver", "data": { "id": "uuid-del-mensaje", "status": "failed" } } ``` Ver [Códigos de Error](/api-reference/errors) para la lista completa. *** ## SDKs y Ejemplos Actualmente no tenemos SDKs oficiales, pero la API es simple de usar con cualquier cliente HTTP: ```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", "content": "Hola!"}' ``` ```javascript Node.js (fetch) 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', content: 'Hola!' }) }); ``` ```python Python (requests) 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', 'content': 'Hola!' } ) ``` ```php PHP (cURL) theme={null} $ch = curl_init('https://api.whaapy.com/messages/v1'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer wha_TU_API_KEY', 'Content-Type: application/json' ]); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'to' => '+5215512345678', 'content' => 'Hola!' ])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); ``` *** ## Próximos Pasos > **[Autenticación](/api-reference/authentication)** > > Configura tu API Key > **[Enviar Mensaje](/api-reference/messages/send)** > > Envía tu primer mensaje > **[Templates](/api-reference/messages/send-templates)** > > Mensajes fuera de ventana 24h > **[Códigos de Error](/api-reference/errors)** > > Manejo de errores # Alta y Baja Masiva Source: https://docs.whaapy.com/api-reference/segments/members POST https://api.whaapy.com/segments/{id}/members Agrega o quita contactos de un segmento estático por ID o con una selección por filtros Agrega o quita muchos contactos de un segmento **estático** en una sola petición. Puedes indicar los contactos por ID, o describir "todos los que coinciden" con una búsqueda y filtros para que el servidor los resuelva. > [!NOTE] > > Solo funciona con segmentos estáticos. Un segmento dinámico no tiene miembros que editar: se calcula con sus reglas. Si usas un dinámico responde `400`. La operación es **idempotente**: agregar dos veces o quitar dos veces el mismo grupo no cambia nada la segunda vez (`affected: 0`). *** ## Path Parameters **id** `string` *required* UUID del segmento estático *** ## Body Parameters **action** `string` *required* `"add"` para agregar o `"remove"` para quitar. Envía **exactamente una** de estas dos formas de elegir contactos (enviar ambas, o ninguna, responde `400`): **contactIds** `string[]` UUID de los contactos. De 1 a 10 000. Todos deben existir y ser de tu negocio; si alguno no, responde `400` y no se aplica nada. Los duplicados se ignoran. Al agregar solo cuentan contactos activos (no eliminados). **search** `string` Texto de búsqueda (máximo 200 caracteres), igual que en la lista de contactos. No puede estar vacío. **filters** `object` Filtros de [Búsqueda Avanzada](/api-reference/contacts/search), incluido `segment_id`. Se combinan con `search` si envías ambos. > [!WARNING] > > Una selección por filtros se valida de forma **estricta**: una clave desconocida (en cualquier nivel), un valor vacío o un filtro sin condiciones responde `400`. Así, "quitar los contactos con el tag `prueba`" nunca se convierte por un error de escritura en "quitar a todos". Tampoco existe un "todos" implícito: la selección debe tener al menos un filtro o una búsqueda. El body no admite campos distintos de `action`, `contactIds`, `search` y `filters`. *** ## Límites | Límite | Valor | | - | - | | `contactIds` por petición | 10 000 | | Contactos de una selección por filtros | **50 000**. Si la selección los supera responde `400` con `Selección demasiado grande; usa filtros más específicos` | | `contactIds` devueltos en la respuesta | Solo si el total afectado es de **10 000 o menos** | Para mover más de 50 000 contactos, divide la selección con filtros más específicos (por ejemplo por tag, origen o fecha de creación) y envía varias peticiones. *** ## Respuesta **data.action** `string` La acción aplicada: `add` o `remove`. **data.affected** `number` Contactos realmente agregados o quitados. Los que ya estaban (al agregar) o que no estaban (al quitar) no cuentan. **data.contactIds** `string[]` IDs de los contactos afectados, útil para ofrecer "Deshacer" enviando la acción contraria con estos IDs. **Solo se devuelve hasta 10 000**; si hay más, el campo se omite y debes usar `affected`. **data.contactCount** `number` Contactos activos del segmento tras la operación. **data.countStale** `boolean` Solo aparece (`true`) si el cambio se aplicó pero no se pudo recalcular el conteo; `contactCount` puede estar desactualizado. *** ## Ejemplos ### Agregar por ID ```bash cURL theme={null} curl -X POST https://api.whaapy.com/segments/7c9e6679-7425-40de-944b-e07fc1f90ae7/members \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "add", "contactIds": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-41d1-80b4-00c04fd430c8" ] }' ``` ### Quitar a todos los que coinciden con un filtro ```bash cURL theme={null} curl -X POST https://api.whaapy.com/segments/7c9e6679-7425-40de-944b-e07fc1f90ae7/members \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "action": "remove", "filters": { "tags": { "any": ["prueba"] }, "created_at": { "within_days": 7 } } }' ``` ```javascript Node.js theme={null} const response = await fetch( 'https://api.whaapy.com/segments/7c9e6679-7425-40de-944b-e07fc1f90ae7/members', { method: 'POST', headers: { 'Authorization': 'Bearer wha_TU_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'remove', filters: { tags: { any: ['prueba'] } } }) } ); ``` ```json 200 OK theme={null} { "data": { "action": "remove", "affected": 2, "contactIds": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-41d1-80b4-00c04fd430c8" ], "contactCount": 340 } } ``` *** ## Errores Todos los `400` tienen la forma `{ "error": "..." }`, salvo los de formato del body, que incluyen `details`. | Código | Causa | | - | - | | `400` | El segmento es dinámico: `Solo se pueden agregar o quitar contactos en segmentos estáticos` | | `400` | Body inválido: `action` ausente o desconocida, campos extra, `contactIds` vacío o con más de 10 000 elementos, o se enviaron `contactIds` junto con `search`/`filters` (o ninguno de los dos) | | `400` | Algún contacto no existe o no es de tu negocio: `Algunos contactos no existen o no pertenecen a tu negocio` | | `400` | `Filtros inválidos (campo "..."): ...` por clave desconocida, valor vacío, filtro sin condiciones o teléfono sin dígitos | | `400` | `La búsqueda no puede estar vacía` o `La selección debe tener al menos un filtro o una búsqueda` | | `400` | `Selección demasiado grande; usa filtros más específicos` (más de 50 000 contactos) | | `404` | El `id` no es un UUID válido, o el segmento no existe o no es de tu negocio | # Segmentos Source: https://docs.whaapy.com/api-reference/segments/overview Agrupa contactos en segmentos estáticos o dinámicos y úsalos en campañas y agentes ## ¿Qué es un segmento? Un segmento es un grupo de contactos de tu negocio. Hay dos tipos: * **Estático**: una lista de contactos que tú agregas y quitas (a mano, por ID o con una selección por filtros). Los segmentos estáticos **no usan reglas**. * **Dinámico**: un **filtro guardado** que se calcula al momento. No guarda una lista de miembros: cada vez que consultas el segmento, o que una campaña lo usa, se evalúa el filtro sobre tus contactos actuales. Un contacto que empieza a cumplir las condiciones entra solo; el que deja de cumplirlas sale solo. Los segmentos dinámicos usan el mismo motor de filtros que [Búsqueda Avanzada de contactos](/api-reference/contacts/search), así que lo que ves en una búsqueda es exactamente lo que contiene el segmento. > [!NOTE] > > Los endpoints de segmentos viven en `https://api.whaapy.com/segments` (sin `/v1`) y se autentican con tu API Key, igual que el resto de la API. *** ## Reglas de un segmento dinámico ### Formato actual: versión 3 ```json theme={null} { "version": 3, "filters": { "tags": { "any": ["cliente", "vip"] }, "funnel_stage_id": "11111111-1111-4111-8111-111111111111", "last_contact_at": { "older_than_days": 30 } } } ``` `filters` acepta los mismos campos que `filters` en [Búsqueda Avanzada](/api-reference/contacts/search) (teléfono, nombre, email, tags, etapa del funnel, origen, fechas, campos personalizados, etc.), con una excepción: **no se permite `segment_id`** dentro de un segmento (evita segmentos que dependen de sí mismos). Se guarda en `snake_case`, como en la API de búsqueda. La validación de v3 es **estricta** a propósito. Si una regla se ignorara en silencio, el segmento sería más grande de lo que esperas y podrías enviar una campaña a contactos equivocados. Por eso, en lugar de ignorarlas, se rechazan. ### Versiones anteriores (v1 y v2) Las reglas heredadas siguen aceptándose al crear, actualizar y previsualizar: * **v1**: `{ "operator": "AND" | "OR", "conditions": [...] }`, sin campo `version`. * **v2**: `{ "version": 2, "operator": "AND" | "OR", "groups": [...] }`. No es necesario migrar. Cuando una regla heredada tiene un equivalente exacto en v3, la API además te devuelve ese equivalente en `filters` (ver abajo). Convertirla nunca modifica lo guardado. *** ## Campos de respuesta Además de los campos habituales (`id`, `name`, `type`, `contactCount`, `color`...), los segmentos devuelven: **contactCount** `number` Número de contactos activos del segmento. En un dinámico se calcula **al momento**. **filters** `object | null` Filtros v3 del segmento. En reglas v3 son los guardados. En reglas v1/v2 solo aparecen cuando la conversión es exacta y se comprobó que ambos motores cuentan lo mismo; esa comprobación se hace al consultar el detalle (`GET /segments/{id}`), por lo que en el listado puede venir `null`. En segmentos estáticos es `null`. **rulesFormat** `string | null` `"filters"` si las reglas guardadas son v3, `"legacy"` si son v1/v2, y `null` en estáticos. **countStale** `boolean` Solo aparece (con valor `true`) cuando no se pudo contar al momento, por ejemplo por un tiempo de espera. En ese caso `contactCount` es el último valor guardado. Vuelve a consultar el segmento o llama a `POST /segments/{id}/evaluate`. **rulesError** `string` Solo aparece cuando las reglas guardadas ya no son válidas. El segmento se sigue devolviendo para que puedas abrirlo y corregirlo, pero **no contiene a nadie** hasta que lo hagas; nunca se amplía la audiencia. **typeChangeIgnored** `boolean` Solo aparece en la respuesta de `PATCH /segments/{id}` cuando enviaste un `type` distinto al actual. El tipo de un segmento no se puede cambiar: se ignora y se avisa con este campo. *** ## Endpoints | Método | Ruta | Descripción | | - | - | - | | `GET` | `/segments` | Lista los segmentos con su conteo al momento | | `POST` | `/segments` | Crea un segmento estático o dinámico | | `POST` | `/segments/preview` | Previsualiza reglas antes de crear el segmento | | `GET` | `/segments/{id}` | Detalle y contactos (`limit` y `offset`) | | `PATCH` | `/segments/{id}` | Actualiza nombre, descripción, color o reglas | | `DELETE` | `/segments/{id}` | Elimina el segmento (no borra contactos) | | `POST` | `/segments/{id}/contacts` | Agrega contactos a un estático por ID | | `DELETE` | `/segments/{id}/contacts/{contactId}` | Quita un contacto de un estático | | `POST` | `/segments/{id}/evaluate` | Recalcula el conteo de un dinámico | | `GET` | [`/segments/{id}/usage`](/api-reference/segments/usage) | Dónde se usa el segmento | | `POST` | [`/segments/{id}/members`](/api-reference/segments/members) | Alta o baja masiva en un estático | ### Crear un segmento dinámico ```bash cURL theme={null} curl -X POST https://api.whaapy.com/segments \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Clientes sin contacto en 30 días", "type": "dynamic", "rules": { "version": 3, "filters": { "tags": { "all": ["cliente"] }, "last_contact_at": { "older_than_days": 30 } } } }' ``` ```json 201 Created theme={null} { "data": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "name": "Clientes sin contacto en 30 días", "type": "dynamic", "contactCount": 128, "rulesFormat": "filters", "filters": { "tags": { "all": ["cliente"] }, "last_contact_at": { "older_than_days": 30 } } } } ``` Un `type` `"dynamic"` requiere `rules`. Un `type` `"static"` no las usa: enviar reglas v3 a un estático responde `400`; reglas heredadas, `null` o vacías se ignoran por compatibilidad. El campo `autoRefreshInterval` se sigue aceptando pero se ignora, porque los dinámicos ya se calculan al momento. ### Previsualizar reglas `POST /segments/preview` recibe `{ "rules": ... }` (v1, v2 o v3) y devuelve `data.count` y `data.preview` (una muestra de contactos) sin guardar nada. *** ## Errores 400 Un segmento rechaza reglas que no se pueden aplicar con seguridad. Todas las respuestas tienen la forma `{ "error": "..." }`. | Causa | Ejemplo de mensaje | | - | - | | Reglas dinámicas ausentes | `Los segmentos dinámicos requieren reglas` | | Reglas v3 en un segmento estático | `Los segmentos estáticos no usan reglas` | | Versión de reglas desconocida | `Versión de reglas no soportada` | | Filtros vacíos (`{}`) o sin ninguna condición | `Regla de segmento inválida ...: se necesita al menos un filtro` | | Valor, lista u objeto vacío (`""`, `[]`, `{}`) | `...: la lista no puede estar vacía` | | Clave desconocida en `filters`, en cualquier nivel (por ejemplo `tags.alll`) | `...: filtro no soportado: alll` | | Clave extra junto a `version` y `filters` | `...: clave no soportada: ...` | | `segment_id` dentro de los filtros del segmento | `...: filtro no soportado: segment_id` | | Teléfono sin dígitos (solo `+`, espacios o guiones) | `...: el teléfono debe tener al menos un dígito` | | Reglas heredadas con campo u operador desconocido | `Regla de segmento inválida (campo "...", operador "..."): ...` | Los mensajes de reglas terminan con `Corrige la regla del segmento e inténtalo de nuevo.` En validaciones de formato (por ejemplo un campo que falta) la respuesta es `{ "error": "Datos inválidos", "details": [...] }`. Otros códigos: `404` si el segmento no existe o no es de tu negocio (nunca se distingue entre ambos casos) y `500` ante un error interno. *** ## Próximos pasos > **[Búsqueda Avanzada](/api-reference/contacts/search)** > > Todos los filtros disponibles > **[Uso de un segmento](/api-reference/segments/usage)** > > Campañas y agentes que lo usan > **[Alta y baja masiva](/api-reference/segments/members)** > > Agregar o quitar miembros de un estático # Uso de un Segmento Source: https://docs.whaapy.com/api-reference/segments/usage GET https://api.whaapy.com/segments/{id}/usage Consulta qué campañas y subagentes usan un segmento Devuelve dónde se usa un [segmento](/api-reference/segments/overview): las campañas que todavía pueden enviar a su audiencia y los subagentes cuya activación depende de él. Es útil para revisar el impacto antes de editar o eliminar un segmento. > [!WARNING] > > Cambiar las reglas de un segmento dinámico, o quitar miembros de uno estático, afecta de inmediato a las campañas y subagentes que aparecen aquí. *** ## Path Parameters **id** `string` *required* UUID del segmento *** ## Respuesta **data.broadcasts** `array` Campañas **vivas** que usan el segmento como origen de destinatarios. Solo se incluyen las que están en estado `draft`, `scheduled`, `sending` o `paused`; las terminadas o fallidas no cuentan. Más recientes primero. **propiedades** **id** `string` UUID de la campaña **name** `string` Nombre de la campaña **status** `string` Estado actual **scheduledAt** `string | null` Próxima tanda o inicio programado del envío (ISO 8601); `null` si no hay fecha **data.subagents** `array` Subagentes del negocio con una regla de activación por este segmento, ordenados por nombre. **propiedades** **id** `string` UUID del subagente **name** `string` Nombre del subagente **isActive** `boolean` Si el subagente está activo ```json 200 OK theme={null} { "data": { "broadcasts": [ { "id": "0b8f3c0e-8c1a-4a77-9b0e-6f1f4f0a1111", "name": "Promo de octubre", "status": "scheduled", "scheduledAt": "2026-10-20T15:00:00.000Z" } ], "subagents": [ { "id": "5d2c9a52-3b1e-4f6a-8d7c-2a9e4c0b2222", "name": "Ventas VIP", "isActive": true } ] } } ``` *** ## Errores | Código | Causa | | - | - | | `404` | El `id` no es un UUID válido, o el segmento no existe o no es de tu negocio | | `500` | Error interno al consultar el uso | *** ## Ejemplo ```bash cURL theme={null} curl https://api.whaapy.com/segments/7c9e6679-7425-40de-944b-e07fc1f90ae7/usage \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch( 'https://api.whaapy.com/segments/7c9e6679-7425-40de-944b-e07fc1f90ae7/usage', { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } } ); const { data } = await response.json(); console.log(data.broadcasts.length, data.subagents.length); ``` # Listar Agentes Source: https://docs.whaapy.com/api-reference/team/list GET https://api.whaapy.com/team/v1 Obtén la lista de agentes humanos de tu equipo con su disponibilidad y conversaciones asignadas Retorna todos los miembros del equipo (agentes humanos) asociados a tu negocio. Útil para obtener IDs de agentes antes de asignarlos a contactos. > [!NOTE] > > **Scope requerido:** `team:read` *** ## Respuesta **agents** `array` **Propiedades de cada agente** **id** `string` UUID del agente. Usa este valor para asignar el agente a un contacto. **name** `string | null` Nombre del agente **email** `string` Email del agente **role** `string` Rol en el equipo: `super_admin`, `admin`, o `agent` **is\_available** `boolean` Si el agente está disponible para recibir asignaciones **assigned\_conversations** `number` Cantidad de conversaciones activas asignadas actualmente *** ## Ejemplos ```bash cURL theme={null} curl https://api.whaapy.com/team/v1 \ -H "Authorization: Bearer wha_TU_API_KEY" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/team/v1', { headers: { 'Authorization': 'Bearer wha_TU_API_KEY' } }); const data = await response.json(); console.log(data.agents); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/team/v1', headers={'Authorization': 'Bearer wha_TU_API_KEY'} ) print(response.json()) ``` ```json 200 OK theme={null} { "agents": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Juan Pérez", "email": "juan@empresa.com", "role": "admin", "is_available": true, "assigned_conversations": 5 }, { "id": "660e8400-e29b-41d4-a716-446655440001", "name": "María García", "email": "maria@empresa.com", "role": "agent", "is_available": true, "assigned_conversations": 3 }, { "id": "770e8400-e29b-41d4-a716-446655440002", "name": "Carlos López", "email": "carlos@empresa.com", "role": "agent", "is_available": false, "assigned_conversations": 0 } ] } ``` *** ## Uso con Asignación de Contactos Una vez que tengas el ID del agente, puedes asignarlo a un contacto usando [PATCH /contacts/v1/:id](/api-reference/contacts/update) o al [crear un contacto](/api-reference/contacts/create): ```bash theme={null} curl -X PATCH https://api.whaapy.com/contacts/v1/CONTACT_ID \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "assigned_agent_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` Al asignar un agente a un contacto: * Todas las conversaciones **activas** del contacto se asignan automáticamente al agente * Las conversaciones **futuras** del contacto también se asignarán al agente automáticamente > **[Crear Contacto](/api-reference/contacts/create)** > > Crea un contacto con agente asignado > **[Actualizar Contacto](/api-reference/contacts/update)** > > Asigna un agente a un contacto existente # Obtener Template Source: https://docs.whaapy.com/api-reference/templates/get GET https://api.whaapy.com/templates/v1/{id} Obtén los detalles de un template específico ## Parámetros de Path **id** `string` *required* UUID del template ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/templates/v1/tmpl-uuid" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const templateId = 'tmpl-uuid'; const response = await fetch( `https://api.whaapy.com/templates/v1/${templateId}`, { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); const data = await response.json(); ``` ```python Python theme={null} import requests template_id = 'tmpl-uuid' response = requests.get( f'https://api.whaapy.com/templates/v1/{template_id}', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```json 200 OK theme={null} { "data": { "id": "tmpl-uuid-1", "name": "order_confirmation", "category": "UTILITY", "language": "es_MX", "status": "APPROVED", "content": "Hola {{1}}, tu pedido #{{2}} ha sido confirmado. Te avisaremos cuando esté en camino.", "variables": ["{{1}}", "{{2}}"], "components": [ { "type": "HEADER", "format": "IMAGE" }, { "type": "BODY", "text": "Hola {{1}}, tu pedido #{{2}} ha sido confirmado. Te avisaremos cuando esté en camino.", "example": { "body_text": [["Juan", "12345"]] } }, { "type": "FOOTER", "text": "Gracias por tu compra" }, { "type": "BUTTONS", "buttons": [ { "type": "URL", "text": "Rastrear pedido", "url": "https://ejemplo.com/track/{{1}}" } ] } ], "createdAt": "2026-01-15T10:00:00Z", "updatedAt": "2026-01-15T10:00:00Z" } } ``` ## Errores ```json 404 Not Found theme={null} { "error": "Template not found" } ``` ## Campos de respuesta | Campo | Tipo | Descripción | | - | - | - | | `id` | string | UUID interno de Whaapy | | `name` | string | Nombre del template (usado para enviar) | | `category` | string | `MARKETING`, `UTILITY`, `AUTHENTICATION` | | `language` | string | Código de idioma | | `status` | string | Estado de aprobación | | `content` | string | Texto del body (para preview) | | `variables` | array | Variables posicionales detectadas | | `components` | array | Estructura completa del template | ## Usar el template Una vez que tienes los detalles del template, puedes usarlo para enviar un mensaje: ```javascript theme={null} const template = response.data; // Enviar el template await fetch('https://api.whaapy.com/messages/v1', { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: '+5215512345678', type: 'template', template: { name: template.name, language: template.language, components: [ { type: 'header', parameters: [ { type: 'image', image: { link: 'https://ejemplo.com/logo.png' } } ] }, { type: 'body', parameters: template.variables.map((_, i) => ({ type: 'text', text: i === 0 ? 'Juan' : '12345' // Reemplazar con valores reales })) }, { type: 'button', sub_type: 'url', index: 0, parameters: [ { type: 'text', text: '12345' } // Valor para {{1}} en la URL ] } ] } }) }); ``` # Listar Templates Source: https://docs.whaapy.com/api-reference/templates/list GET https://api.whaapy.com/templates/v1 Obtén todos los templates de WhatsApp aprobados ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/templates/v1" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/templates/v1', { headers: { 'Authorization': 'Bearer wha_xxxxx' } }); const { data } = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/templates/v1', headers={'Authorization': 'Bearer wha_xxxxx'} ) templates = response.json()['data'] ``` ```php PHP theme={null} $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/templates/v1", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "data": [ { "id": "tmpl-uuid-1", "name": "order_confirmation", "category": "UTILITY", "language": "es_MX", "status": "APPROVED", "content": "Hola {{1}}, tu pedido #{{2}} ha sido confirmado.", "variables": ["{{1}}", "{{2}}"], "components": [ { "type": "BODY", "text": "Hola {{1}}, tu pedido #{{2}} ha sido confirmado.", "example": { "body_text": [["Juan", "12345"]] } } ], "createdAt": "2026-01-15T10:00:00Z", "updatedAt": "2026-01-15T10:00:00Z" }, { "id": "tmpl-uuid-2", "name": "welcome_message", "category": "MARKETING", "language": "es_MX", "status": "APPROVED", "content": "¡Bienvenido {{1}}! Gracias por contactarnos.", "variables": ["{{1}}"], "components": [ { "type": "HEADER", "format": "IMAGE" }, { "type": "BODY", "text": "¡Bienvenido {{1}}! Gracias por contactarnos." }, { "type": "FOOTER", "text": "Equipo de Soporte" }, { "type": "BUTTONS", "buttons": [ { "type": "QUICK_REPLY", "text": "Ver catálogo" }, { "type": "URL", "text": "Visitar web", "url": "https://ejemplo.com" } ] } ], "createdAt": "2026-01-10T08:00:00Z", "updatedAt": "2026-01-10T08:00:00Z" } ] } ``` ## Campos del template | Campo | Tipo | Descripción | | - | - | - | | `id` | string | UUID interno de Whaapy | | `name` | string | Nombre del template (slug) | | `category` | string | `MARKETING`, `UTILITY`, `AUTHENTICATION` | | `language` | string | Código de idioma (ej: `es_MX`, `en_US`) | | `status` | string | `APPROVED`, `PENDING`, `REJECTED` | | `content` | string | Texto del body con placeholders | | `variables` | array | Lista de variables detectadas | | `components` | array | Componentes del template | ## Estructura de componentes Cada template puede tener los siguientes componentes: ### HEADER ```json theme={null} { "type": "HEADER", "format": "TEXT" | "IMAGE" | "VIDEO" | "DOCUMENT", "text": "Texto del header (solo si format=TEXT)", "example": { "header_text": ["Ejemplo"] } } ``` ### BODY ```json theme={null} { "type": "BODY", "text": "Texto con {{1}} variables {{2}}", "example": { "body_text": [["valor1", "valor2"]] } } ``` ### FOOTER ```json theme={null} { "type": "FOOTER", "text": "Texto del footer" } ``` ### BUTTONS ```json theme={null} { "type": "BUTTONS", "buttons": [ { "type": "QUICK_REPLY", "text": "Opción 1" }, { "type": "URL", "text": "Visitar web", "url": "https://ejemplo.com/{{1}}" }, { "type": "PHONE_NUMBER", "text": "Llamar", "phone_number": "+5215512345678" } ] } ``` > [!NOTE] > > Solo se devuelven templates con estado `APPROVED`. Para ver templates pendientes o rechazados, usa el dashboard de Whaapy o el Meta Business Manager. ## Usar template para enviar mensaje Una vez que tienes el template, puedes enviarlo así: ```javascript theme={null} const template = templates[0]; // order_confirmation await fetch('https://api.whaapy.com/messages/v1', { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: '+5215512345678', type: 'template', template: { name: template.name, language: template.language, components: [ { type: 'body', parameters: [ { type: 'text', text: 'Juan' }, // {{1}} { type: 'text', text: '12345' } // {{2}} ] } ] } }) }); ``` # Templates Source: https://docs.whaapy.com/api-reference/templates/overview Consulta y gestiona templates de WhatsApp aprobados # Templates de WhatsApp Los templates son mensajes pre-aprobados por Meta que puedes enviar **fuera de la ventana de 24 horas**. Son esenciales para iniciar conversaciones con clientes que no han escrito recientemente. > **[Listar Templates](/api-reference/templates/list)** > > Obtén todos los templates aprobados > **[Obtener Template](/api-reference/templates/get)** > > Detalles de un template específico > **[Variables](/api-reference/templates/variables)** > > Variables disponibles para personalización > **[Sincronizar](/api-reference/templates/sync)** > > Sincroniza templates desde Meta ## Scopes requeridos | Endpoint | Scope | | - | - | | Lectura (GET) | `templates:read` | | Sincronización | `templates:write` | ## ¿Cuándo usar templates? > **Iniciar conversación** > > Si el cliente no te ha escrito en las últimas 24 horas, debes usar un template para iniciar. > **Mensajes masivos (broadcasts)** > > Para campañas de marketing o notificaciones a múltiples contactos. > **Notificaciones transaccionales** > > Confirmaciones de pedidos, actualizaciones de envío, recordatorios de citas. ## Enviar Templates Para enviar templates, usa el endpoint de mensajes con `type: "template"`: ```bash theme={null} POST /messages/v1 { "to": "+5215512345678", "type": "template", "template": { "name": "order_confirmation", "language": "es_MX", "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Juan" }, { "type": "text", "text": "12345" } ] } ] } } ``` Ver [Enviar Templates](/api-reference/messages/send-templates) para documentación completa. ## Estados de Template | Estado | Descripción | ¿Se puede enviar? | | - | - | - | | `APPROVED` | Aprobado por Meta | ✅ Sí | | `PENDING` | En revisión por Meta | ❌ No | | `REJECTED` | Rechazado (ver razón) | ❌ No | > [!WARNING] > > Solo los templates con estado `APPROVED` pueden ser enviados. Los templates `PENDING` o `REJECTED` darán error al intentar enviarlos. ## Categorías de Template | Categoría | Uso | Costo | | - | - | - | | `UTILITY` | Transaccionales (pedidos, envíos, citas) | Menor | | `MARKETING` | Promociones, ofertas, newsletters | Mayor | | `AUTHENTICATION` | Códigos OTP, verificación | Menor | ## Flujo de trabajo ```mermaid theme={null} flowchart LR A[Crear template en Meta Business Manager] --> B[Meta revisa 24-48h] B --> C{Aprobado?} C -->|Sí| D[Sincronizar con Whaapy] C -->|No| E[Revisar razón y corregir] D --> F[Listo para enviar] E --> A ``` ## Próximos pasos > **[Enviar Templates](/api-reference/messages/send-templates)** > > Aprende a enviar templates con variables > **[Variables Disponibles](/api-reference/templates/variables)** > > Personaliza templates con datos del contacto # Sincronizar Templates Source: https://docs.whaapy.com/api-reference/templates/sync POST https://api.whaapy.com/templates/v1/sync Sincroniza templates desde Meta manualmente Fuerza una sincronización de templates desde la API de Meta. Útil después de crear o modificar templates desde el Meta Business Manager. > [!NOTE] > > La sincronización es automática cada 15 minutos. Usa este endpoint solo si necesitas ver cambios inmediatamente. ```bash cURL theme={null} curl -X POST "https://api.whaapy.com/templates/v1/sync" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const response = await fetch('https://api.whaapy.com/templates/v1/sync', { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx' } }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.whaapy.com/templates/v1/sync', headers={'Authorization': 'Bearer wha_xxxxx'} ) data = response.json() ``` ```php PHP theme={null} $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => "https://api.whaapy.com/templates/v1/sync", CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer wha_xxxxx" ], ]); $response = curl_exec($curl); $data = json_decode($response, true); ``` ```json 200 OK theme={null} { "data": { "synced": 5, "created": 2, "updated": 1, "deleted": 0 }, "message": "Sincronización completada: 5 templates sincronizados" } ``` ## Campos de respuesta | Campo | Tipo | Descripción | | - | - | - | | `synced` | number | Total de templates procesados | | `created` | number | Nuevos templates encontrados | | `updated` | number | Templates actualizados (status, content) | | `deleted` | number | Templates eliminados en Meta | ## Qué sincroniza La sincronización actualiza: 1. **Nuevos templates** - Templates creados en Meta Business Manager 2. **Estado de aprobación** - `PENDING` → `APPROVED` o `REJECTED` 3. **Contenido** - Cambios en texto o componentes 4. **Templates eliminados** - Se marcan como inactivos ## Flujo de trabajo típico ```mermaid theme={null} sequenceDiagram participant U as Usuario participant M as Meta Business Manager participant W as Whaapy API U->>M: Crea template M->>M: Revisa (24-48h) M-->>M: Aprueba template U->>W: POST /templates/v1/sync W->>M: Obtiene templates M-->>W: Lista actualizada W-->>U: {created: 1} U->>W: GET /templates/v1 W-->>U: Template disponible ``` ## Errores ```json 404 Not Found theme={null} { "error": "WhatsApp no configurado" } ``` ```json 500 Error theme={null} { "error": "Error al sincronizar templates", "message": "Meta API error: Invalid access token" } ``` ## Casos de uso > **Después de crear template en Meta** > > Cuando creas un nuevo template en Meta Business Manager y quieres usarlo inmediatamente: > > ```javascript theme={null} > // 1. Espera aprobación de Meta (verifica en Business Manager) > > // 2. Sincroniza con Whaapy > await fetch('https://api.whaapy.com/templates/v1/sync', { > method: 'POST', > headers: { 'Authorization': 'Bearer wha_xxxxx' } > }); > > // 3. Verifica que el template está disponible > const templates = await fetch('https://api.whaapy.com/templates/v1', { > headers: { 'Authorization': 'Bearer wha_xxxxx' } > }).then(r => r.json()); > > const newTemplate = templates.data.find(t => t.name === 'mi_nuevo_template'); > console.log('Template disponible:', newTemplate?.status === 'APPROVED'); > ``` > **Verificar estado de template pendiente** > > Para verificar si un template fue aprobado: > > ```javascript theme={null} > // Sincronizar para obtener estado actualizado > await fetch('https://api.whaapy.com/templates/v1/sync', { > method: 'POST', > headers: { 'Authorization': 'Bearer wha_xxxxx' } > }); > > // Verificar estado > const template = await fetch('https://api.whaapy.com/templates/v1/tmpl-uuid', { > headers: { 'Authorization': 'Bearer wha_xxxxx' } > }).then(r => r.json()); > > if (template.data.status === 'APPROVED') { > console.log('Template aprobado, listo para usar'); > } else if (template.data.status === 'REJECTED') { > console.log('Template rechazado'); > } else { > console.log('Template aún en revisión'); > } > ``` > [!TIP] > > Meta tarda típicamente 24-48 horas en revisar nuevos templates. Si el template sigue en `PENDING` después de sincronizar, verifica en Meta Business Manager si hay observaciones o rechazos. # Variables Disponibles Source: https://docs.whaapy.com/api-reference/templates/variables GET https://api.whaapy.com/templates/v1/variables Lista las variables dinámicas disponibles para templates Las variables te permiten personalizar los templates con datos del contacto, negocio o sistema. Whaapy resuelve automáticamente estas variables al enviar un mensaje. ## Parámetros **contactId** `string` UUID del contacto para obtener valores de ejemplo resueltos ```bash cURL theme={null} curl -X GET "https://api.whaapy.com/templates/v1/variables" \ -H "Authorization: Bearer wha_xxxxx" ``` ```javascript Node.js theme={null} const response = await fetch( 'https://api.whaapy.com/templates/v1/variables', { headers: { 'Authorization': 'Bearer wha_xxxxx' } } ); const { data } = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.whaapy.com/templates/v1/variables', headers={'Authorization': 'Bearer wha_xxxxx'} ) variables = response.json()['data'] ``` ```json 200 OK theme={null} { "data": { "contact": { "name": "{{contact.name}}", "first_name": "{{contact.first_name}}", "last_name": "{{contact.last_name}}", "phone": "{{contact.phone}}", "email": "{{contact.email}}" }, "business": { "name": "{{business.name}}", "phone": "{{business.phone}}", "email": "{{business.email}}", "address": "{{business.address}}", "website": "{{business.website}}" }, "system": { "date": "{{system.date}}", "time": "{{system.time}}", "datetime": "{{system.datetime}}" } } } ``` ## Variables del contacto | Variable | Descripción | Ejemplo | | - | - | - | | `{{contact.name}}` | Nombre completo | Juan Pérez | | `{{contact.first_name}}` | Primer nombre | Juan | | `{{contact.last_name}}` | Apellido | Pérez | | `{{contact.phone}}` | Número de teléfono | +5215512345678 | | `{{contact.email}}` | Email | [juan@ejemplo.com](mailto:juan@ejemplo.com) | ## Variables del negocio | Variable | Descripción | Ejemplo | | - | - | - | | `{{business.name}}` | Nombre del negocio | Mi Empresa | | `{{business.phone}}` | Teléfono del negocio | +5215500000000 | | `{{business.email}}` | Email del negocio | [info@miempresa.com](mailto:info@miempresa.com) | | `{{business.address}}` | Dirección | Av. Reforma 123 | | `{{business.website}}` | Sitio web | [https://miempresa.com](https://miempresa.com) | ## Variables del sistema | Variable | Descripción | Ejemplo | | - | - | - | | `{{system.date}}` | Fecha actual | 29/01/2026 | | `{{system.time}}` | Hora actual | 10:30 | | `{{system.datetime}}` | Fecha y hora | 29/01/2026 10:30 | ## Uso en templates Puedes usar estas variables al enviar un template: ```javascript theme={null} await fetch('https://api.whaapy.com/messages/v1', { method: 'POST', headers: { 'Authorization': 'Bearer wha_xxxxx', 'Content-Type': 'application/json' }, body: JSON.stringify({ to: '+5215512345678', type: 'template', template: { name: 'welcome_message', language: 'es_MX', components: [ { type: 'body', parameters: [ // Whaapy resolverá automáticamente el nombre del contacto { type: 'text', text: '{{contact.name}}' } ] } ] } }) }); ``` > [!TIP] > > Whaapy resuelve las variables automáticamente al enviar el mensaje. Si una variable no tiene valor (ej: el contacto no tiene email), se enviará vacía o con un valor por defecto. ## Ver valores resueltos Para previsualizar cómo se verán las variables para un contacto específico: ```bash theme={null} curl "https://api.whaapy.com/templates/v1/variables?contactId=uuid-del-contacto" \ -H "Authorization: Bearer wha_xxxxx" ``` Esto te devolverá las variables con sus valores resueltos para ese contacto. # Eventos Source: https://docs.whaapy.com/api-reference/webhooks/events Lista completa de eventos disponibles para webhooks # Eventos Disponibles Puedes suscribirte a cualquiera de estos eventos al crear o actualizar tu webhook. *** ## Mensajes Eventos relacionados con el envío y recepción de mensajes. | Evento | Descripción | | - | - | | `message.received` | Un cliente envió un mensaje | | `message.sent` | Un mensaje fue enviado (manual o por IA) | | `message.delivered` | WhatsApp confirmó la entrega | | `message.read` | El cliente leyó el mensaje | | `message.failed` | El mensaje no se pudo enviar | > [!TIP] > > El evento `message.received` incluye el mensaje en formato Meta original. Ver [Payloads](/api-reference/webhooks/payloads) para detalles. *** ## Conversaciones Eventos relacionados con la gestión de conversaciones. | Evento | Descripción | | - | - | | `conversation.created` | Nueva conversación iniciada | | `conversation.assigned` | Conversación asignada a un agente | | `conversation.unassigned` | Conversación desasignada | | `conversation.closed` | Conversación cerrada | *** ## Contactos Eventos relacionados con la gestión de contactos. | Evento | Descripción | | - | - | | `contact.created` | Nuevo contacto creado | | `contact.updated` | Contacto actualizado | | `contact.deleted` | Contacto eliminado | *** ## Broadcasts (Remarketing) Eventos relacionados con envíos masivos. | Evento | Descripción | | - | - | | `broadcast.sent` | Broadcast iniciado | | `broadcast.completed` | Broadcast completado | | `broadcast.failed` | Broadcast falló | *** ## Wildcard Para suscribirte a **todos** los eventos, usa el wildcard: | Evento | Descripción | | - | - | | `*` | Recibe todos los eventos | > [!WARNING] > > El wildcard puede generar un alto volumen de requests. Úsalo con precaución. *** ## Ejemplo de Suscripción ```bash theme={null} curl -X POST https://api.whaapy.com/user-webhooks \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Todos los mensajes", "url": "https://mi-servidor.com/webhooks", "events": ["message.received", "message.sent", "message.delivered", "message.read"] }' ``` *** ## Próximos Pasos > **[Estructura de Payloads](/api-reference/webhooks/payloads)** > > Ver qué datos recibes en cada evento > **[Gestión de Webhooks](/api-reference/webhooks/management)** > > Actualizar eventos suscritos # Gestión Source: https://docs.whaapy.com/api-reference/webhooks/management Administrar, probar y monitorear tus webhooks # Gestión de Webhooks Endpoints para administrar tus webhooks: listar, actualizar, probar, ver logs y eliminar. *** ## Listar Webhooks Obtén todos los webhooks configurados. ```bash theme={null} curl -X GET https://api.whaapy.com/user-webhooks \ -H "Authorization: Bearer wha_TU_API_KEY" ``` **Response:** ```json theme={null} { "data": [ { "id": "wh-uuid", "name": "Mi Webhook", "url": "https://mi-servidor.com/webhooks", "events": ["message.received"], "isActive": true, "failureCount": 0, "createdAt": "2025-01-15T10:00:00Z" } ] } ``` *** ## Actualizar Webhook Modifica los eventos o estado de un webhook. ```bash theme={null} curl -X PATCH https://api.whaapy.com/user-webhooks/ \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "events": ["message.received", "message.sent"], "isActive": true }' ``` | Campo | Tipo | Descripción | | - | - | - | | `name` | string | Nuevo nombre | | `url` | string | Nueva URL | | `events` | string\[] | Nueva lista de eventos | | `isActive` | boolean | Activar/desactivar | *** ## Probar Webhook Envía un evento de prueba para verificar tu endpoint. ```bash theme={null} curl -X POST https://api.whaapy.com/user-webhooks//test \ -H "Authorization: Bearer wha_TU_API_KEY" ``` **Response:** ```json theme={null} { "success": true, "message": "Test webhook sent successfully", "data": { "status": 200, "duration_ms": 150, "response_preview": "{\"received\":true}" } } ``` > [!TIP] > > Usa el endpoint de test para verificar que tu servidor responde correctamente antes de activar el webhook. *** ## Ver Logs Obtén el historial de entregas de un webhook. ```bash theme={null} curl -X GET "https://api.whaapy.com/user-webhooks//logs?limit=50" \ -H "Authorization: Bearer wha_TU_API_KEY" ``` **Response:** ```json theme={null} { "data": [ { "id": "log-uuid", "event": "message.received", "status": 200, "duration_ms": 120, "timestamp": "2025-01-15T10:30:00Z", "error": null }, { "id": "log-uuid-2", "event": "message.sent", "status": 500, "duration_ms": 30000, "timestamp": "2025-01-15T10:31:00Z", "error": "Connection timeout" } ] } ``` *** ## Eliminar Webhook ```bash theme={null} curl -X DELETE https://api.whaapy.com/user-webhooks/ \ -H "Authorization: Bearer wha_TU_API_KEY" ``` *** ## Política de Reintentos Cuando tu endpoint falla, Whaapy reintenta automáticamente: | Intento | Delay | Tiempo total | | - | - | - | | 1 | Inmediato | 0s | | 2 | 2 segundos | 2s | | 3 | 4 segundos | 6s | | 4 | 8 segundos | 14s | > [!NOTE] > > **Máximo 3 reintentos** con backoff exponencial. Timeout por request: 30 segundos. *** ## Desactivación Automática Si un webhook falla **10 veces consecutivas**, se desactiva automáticamente para proteger ambos sistemas. Para reactivarlo: ```bash theme={null} curl -X PATCH https://api.whaapy.com/user-webhooks/ \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{"isActive": true}' ``` > [!WARNING] > > Antes de reactivar, verifica que tu endpoint esté funcionando correctamente. *** ## Mejores Prácticas ### 1. Responde Rápido Tu endpoint debe responder en menos de 30 segundos. Procesa async si necesitas hacer trabajo pesado. **Node.js** ```javascript theme={null} app.post('/webhook', async (req, res) => { // Responde inmediatamente res.json({ received: true }); // Procesa después (async) await processWebhook(req.body).catch(console.error); }); ``` **Python** ```python theme={null} from threading import Thread @app.route('/webhook', methods=['POST']) def webhook(): payload = request.get_json() # Responde inmediatamente response = jsonify({'received': True}) # Procesa en background Thread(target=process_webhook, args=(payload,)).start() return response ``` ### 2. Implementa Idempotencia Los webhooks pueden llegar más de una vez. Usa el timestamp y evento para detectar duplicados. ```javascript theme={null} const processedEvents = new Set(); app.post('/webhook', (req, res) => { const eventId = `${req.body.event}-${req.body.timestamp}`; if (processedEvents.has(eventId)) { return res.json({ received: true, duplicate: true }); } processedEvents.add(eventId); // Procesar... res.json({ received: true }); }); ``` > [!TIP] > > En producción, usa Redis o una base de datos en lugar de un Set en memoria. ### 3. Verifica la Firma **Siempre** verifica la firma HMAC antes de procesar. Ver [Seguridad](/api-reference/webhooks/security). ### 4. Usa HTTPS Los webhooks solo se envían a endpoints HTTPS. Para desarrollo local, usa: * [ngrok](https://ngrok.com) - Túnel HTTPS gratuito * [localtunnel](https://localtunnel.github.io/www/) - Alternativa open source * [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) - Opción empresarial ### 5. Loggea para Debugging ```javascript theme={null} app.post('/webhook', (req, res) => { console.log('Webhook received:', { event: req.body.event, timestamp: req.headers['x-webhook-timestamp'], id: req.headers['x-webhook-id'], }); // Procesar... }); ``` *** ## Ejemplo Completo: Integración con CRM ```javascript theme={null} const express = require('express'); const crypto = require('crypto'); const axios = require('axios'); const app = express(); app.use(express.json()); const WEBHOOK_SECRET = process.env.WHAAPY_WEBHOOK_SECRET; const CRM_API = 'https://mi-crm.com/api'; function verifySignature(payload, signature) { const expected = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } app.post('/webhooks/whaapy', async (req, res) => { // 1. Verificar firma const signature = req.headers['x-webhook-signature']; if (!verifySignature(req.body, signature)) { return res.status(401).json({ error: 'Invalid signature' }); } // 2. Responder inmediatamente res.json({ received: true }); // 3. Procesar según evento const { event, data } = req.body; try { switch (event) { case 'message.received': // Extraer texto del formato Meta const text = data.message?.text?.body || '[Media]'; await axios.post(`${CRM_API}/activities`, { type: 'whatsapp_message', contact_phone: data.message.from, content: text, direction: 'inbound', whaapy_id: data.whaapy_message_id, }); break; case 'contact.created': await axios.post(`${CRM_API}/contacts`, { phone: data.phone_number, name: data.name, source: 'whaapy', }); break; case 'conversation.closed': await axios.patch(`${CRM_API}/deals`, { phone: data.phone_number, status: 'contacted', }); break; } } catch (error) { console.error('Error procesando webhook:', error.message); } }); app.listen(3000, () => { console.log('Webhook server running on port 3000'); }); ``` *** ## Recursos Adicionales > **[Dashboard](https://app.whaapy.com/settings/webhooks)** > > Gestiona webhooks desde la UI > **[Eventos Disponibles](/api-reference/webhooks/events)** > > Lista completa de eventos # Webhooks Source: https://docs.whaapy.com/api-reference/webhooks/overview Recibe notificaciones en tiempo real cuando ocurren eventos en tu cuenta # Webhooks Los webhooks de Whaapy te permiten recibir notificaciones en tiempo real cuando ocurren eventos en tu cuenta. En lugar de hacer polling constante, configura un endpoint HTTP y recibe actualizaciones automáticamente. *** ## ¿Por qué usar Webhooks? > **Tiempo Real** > > Recibe eventos al instante, sin delays > **Eficiente** > > Sin polling constante a nuestra API > **Escalable** > > Procesa miles de eventos sin problemas *** ## Crear un Webhook > [!NOTE] > > Necesitas un endpoint HTTPS público para recibir webhooks. Para desarrollo local, usa herramientas como [ngrok](https://ngrok.com). **Endpoint**: `POST /user-webhooks` ```bash theme={null} curl -X POST https://api.whaapy.com/user-webhooks \ -H "Authorization: Bearer wha_TU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Mi Webhook de Producción", "url": "https://mi-servidor.com/webhooks/whaapy", "events": ["message.received", "message.sent", "conversation.created"], "metadata": { "description": "Webhook para sincronizar con CRM" } }' ``` ### Parámetros | Campo | Tipo | Requerido | Descripción | | - | - | - | - | | `name` | string | Sí | Nombre descriptivo del webhook | | `url` | string | Sí | URL HTTPS donde recibirás los eventos | | `events` | string\[] | Sí | Lista de eventos a suscribir (ver [Eventos](/api-reference/webhooks/events)) | | `metadata` | object | No | Datos adicionales para tu referencia | ### Response ```json theme={null} { "data": { "id": "wh-uuid", "name": "Mi Webhook de Producción", "url": "https://mi-servidor.com/webhooks/whaapy", "events": ["message.received", "message.sent", "conversation.created"], "secret": "a1b2c3d4e5f6...", "isActive": true, "failureCount": 0, "createdAt": "2025-01-15T10:00:00Z" }, "message": "Webhook created successfully" } ``` > [!WARNING] > > **Guarda el `secret` de forma segura.** Lo necesitarás para [verificar la firma](/api-reference/webhooks/security) de los webhooks. No podrás verlo de nuevo. *** ## Estructura del Payload Todos los webhooks que recibirás tienen esta estructura base: ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid-del-negocio", "data": { // Datos específicos del evento } } ``` | Campo | Tipo | Descripción | | - | - | - | | `event` | string | Tipo de evento (ej: `message.received`) | | `timestamp` | string | Fecha/hora ISO 8601 del evento | | `businessId` | string | UUID de tu negocio | | `data` | object | Datos específicos del evento | Ver [Payloads](/api-reference/webhooks/payloads) para ejemplos detallados de cada evento. *** ## Próximos Pasos > **[Eventos Disponibles](/api-reference/webhooks/events)** > > Lista completa de eventos que puedes suscribir > **[Estructura de Payloads](/api-reference/webhooks/payloads)** > > Ejemplos detallados de cada tipo de evento > **[Seguridad](/api-reference/webhooks/security)** > > Cómo verificar la autenticidad de los webhooks > **[Gestión](/api-reference/webhooks/management)** > > Actualizar, probar y eliminar webhooks # Estructura de payloads Source: https://docs.whaapy.com/api-reference/webhooks/payloads Estructura detallada de los payloads de webhook # Estructura de Payloads Cada evento tiene una estructura de payload específica. Esta página documenta los más importantes. *** ## message.received El payload de `message.received` usa una **estructura híbrida**: el campo `message` contiene el mensaje **exactamente como lo envía Meta**, más IDs propios de Whaapy y extras de valor agregado. ### Campos Base Estos campos siempre están presentes: | Campo | Tipo | Descripción | | - | - | - | | `whaapy_message_id` | string | ID interno del mensaje en Whaapy | | `conversation_id` | string | ID de la conversación en Whaapy | | `contact_id` | string | ID del contacto en Whaapy | | `message` | object | **Mensaje original de Meta** (estructura idéntica a la API de Meta) | | `contact_name` | string | Nombre del contacto (si está disponible) | ### Campos Extras (Valor Agregado) Estos campos aparecen según el tipo de mensaje: | Campo | Tipo | Cuándo aparece | Descripción | | - | - | - | - | | `media_url` | string | image, video, audio, document, sticker | URL con token (24h) para descargar media | | `transcription` | string | audio | Texto transcrito de la nota de voz | > [!TIP] > > **¿Por qué estructura híbrida?** El campo `message` usa exactamente el formato de Meta, permitiéndote reutilizar código existente. Los extras de Whaapy (`media_url`, `transcription`) te ahorran llamadas adicionales a la API. *** ### Ejemplos por Tipo de Mensaje **Texto** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "text", "text": { "body": "Hola, necesito ayuda" } }, "contact_name": "Juan Pérez" } } ``` **Audio** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "audio", "audio": { "mime_type": "audio/ogg; codecs=opus", "sha256": "abc123def456...", "id": "media_id_de_meta", "voice": true } }, "contact_name": "Juan Pérez", "media_url": "https://api.whaapy.com/messages/msg-uuid/media?token=...", "transcription": "Hola, quería preguntar sobre el precio del producto" } } ``` > [!NOTE] > > `transcription` solo aparece si la transcripción fue exitosa. El `media_url` tiene un token válido por 24 horas. **Imagen** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "image", "image": { "caption": "Mira esta foto del producto", "mime_type": "image/jpeg", "sha256": "abc123def456...", "id": "media_id_de_meta" } }, "contact_name": "Juan Pérez", "media_url": "https://api.whaapy.com/messages/msg-uuid/media?token=..." } } ``` **Video** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "video", "video": { "caption": "Video del problema que tengo", "mime_type": "video/mp4", "sha256": "abc123def456...", "id": "media_id_de_meta" } }, "contact_name": "Juan Pérez", "media_url": "https://api.whaapy.com/messages/msg-uuid/media?token=..." } } ``` **Documento** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "document", "document": { "filename": "factura-enero-2025.pdf", "mime_type": "application/pdf", "sha256": "abc123def456...", "id": "media_id_de_meta" } }, "contact_name": "Juan Pérez", "media_url": "https://api.whaapy.com/messages/msg-uuid/media?token=..." } } ``` **Ubicación** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "location", "location": { "latitude": 19.4326, "longitude": -99.1332, "name": "Mi Casa", "address": "Av. Reforma 123, CDMX" } }, "contact_name": "Juan Pérez" } } ``` **Contacto** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "contacts", "contacts": [ { "name": { "formatted_name": "María García", "first_name": "María", "last_name": "García" }, "phones": [ { "phone": "+5215598765432", "type": "CELL", "wa_id": "5215598765432" } ], "emails": [ { "email": "maria@example.com", "type": "WORK" } ] } ] }, "contact_name": "Juan Pérez" } } ``` **Sticker** ```json theme={null} { "event": "message.received", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1705312200", "type": "sticker", "sticker": { "mime_type": "image/webp", "sha256": "abc123def456...", "id": "media_id_de_meta", "animated": false } }, "contact_name": "Juan Pérez", "media_url": "https://api.whaapy.com/messages/msg-uuid/media?token=..." } } ``` **Flow Reply** ```json theme={null} { "event": "message.received", "timestamp": "2026-03-09T18:30:00.000Z", "businessId": "uuid", "data": { "whaapy_message_id": "msg-uuid", "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "message": { "from": "5215512345678", "id": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "timestamp": "1741545000", "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\"}}" } }, "context": { "id": "wamid.HBgNflowOriginal" } }, "contact_name": "Juan Pérez" } } ``` > [!NOTE] > > Cuando Whaapy recibe un `interactive.type = "nfm_reply"`, lo persiste como mensaje `interactive` en el inbox y mantiene el payload original de Meta dentro de `message.interactive.nfm_reply`. *** ## Sobre media\_url vs message.\*.id > **message.image.id (Meta)** > > ID de media de Meta. Requiere llamada adicional a su API para obtener la URL de descarga. > **media\_url (Whaapy)** > > URL lista para descargar con token incluido. Sin llamadas adicionales. **Limitaciones:** * **Token válido**: 24 horas * **Expiración de media**: WhatsApp elimina media después de 30 días > [!TIP] > > Para referencia del formato Meta, consulta la [documentación oficial](https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components#messages-object). *** ## message.sent Cuando tu negocio envía un mensaje (manual o por IA). ```json theme={null} { "event": "message.sent", "timestamp": "2025-01-15T10:31:00.000Z", "businessId": "uuid", "data": { "message_id": "msg-uuid", "conversation_id": "conv-uuid", "to": "+5215512345678", "contact_name": "Juan Pérez", "type": "text", "content": "¡Hola! ¿En qué puedo ayudarte?", "sent_by_ai": true, "timestamp": "2025-01-15T10:31:00.000Z", "provider": "meta" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `message_id` | string | ID del mensaje en Whaapy | | `conversation_id` | string | ID de la conversación | | `to` | string | Número de destino | | `type` | string | Tipo de mensaje (text, image, template, etc.) | | `content` | string | Contenido del mensaje | | `sent_by_ai` | boolean | `true` si fue enviado por el agente de IA | *** ## conversation.created Cuando se inicia una nueva conversación. ```json theme={null} { "event": "conversation.created", "timestamp": "2025-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "conversation_id": "conv-uuid", "contact_id": "contact-uuid", "phone_number": "+5215512345678", "contact_name": "Juan Pérez", "timestamp": "2025-01-15T10:30:00.000Z" } } ``` *** ## conversation.assigned Cuando una conversación se asigna a un agente humano. ```json theme={null} { "event": "conversation.assigned", "timestamp": "2025-01-15T10:32:00.000Z", "businessId": "uuid", "data": { "conversation_id": "conv-uuid", "phone_number": "+5215512345678", "contact_name": "Juan Pérez", "assigned_to": "agent-uuid", "assigned_by": "admin-uuid", "method": "manual", "timestamp": "2025-01-15T10:32:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `assigned_to` | string | UUID del agente asignado | | `assigned_by` | string | UUID de quien asignó | | `method` | string | `manual` o `auto` | *** ## broadcast.completed Cuando un envío masivo termina. ```json theme={null} { "event": "broadcast.completed", "timestamp": "2025-01-15T11:00:00.000Z", "businessId": "uuid", "data": { "broadcast_id": "bc-uuid", "name": "Promoción Enero", "total_recipients": 500, "sent_count": 495, "failed_count": 5, "success_rate": 99, "completed_at": "2025-01-15T11:00:00.000Z" } } ``` *** ## message.delivered Cuando WhatsApp confirma que el mensaje fue entregado al dispositivo del usuario. ```json theme={null} { "event": "message.delivered", "timestamp": "2026-01-15T10:31:05.000Z", "businessId": "uuid", "data": { "message_id": "msg-uuid", "wamid": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "conversation_id": "conv-uuid", "to": "+5215512345678", "contact_name": "Juan Pérez", "status": "delivered", "timestamp": "2026-01-15T10:31:05.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `message_id` | string | ID del mensaje en Whaapy | | `wamid` | string | ID del mensaje en WhatsApp | | `status` | string | Siempre `delivered` | *** ## message.read Cuando el usuario abre y lee el mensaje (doble check azul). ```json theme={null} { "event": "message.read", "timestamp": "2026-01-15T10:35:00.000Z", "businessId": "uuid", "data": { "message_id": "msg-uuid", "wamid": "wamid.HBgNNTIxNTUxMjM0NTY3OA==", "conversation_id": "conv-uuid", "to": "+5215512345678", "contact_name": "Juan Pérez", "status": "read", "timestamp": "2026-01-15T10:35:00.000Z" } } ``` > [!NOTE] > > El evento `message.read` solo se recibe si el usuario tiene habilitada la confirmación de lectura en WhatsApp. *** ## message.failed Cuando un mensaje no se pudo enviar. ```json theme={null} { "event": "message.failed", "timestamp": "2026-01-15T10:31:00.000Z", "businessId": "uuid", "data": { "message_id": "msg-uuid", "conversation_id": "conv-uuid", "to": "+5215512345678", "contact_name": "Juan Pérez", "status": "failed", "error": { "code": "131047", "title": "Re-engagement message", "message": "La ventana de conversación de 24 horas ha expirado", "details": "More than 24 hours have passed since the customer last replied" }, "timestamp": "2026-01-15T10:31:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `error.code` | string | Código de error de WhatsApp | | `error.title` | string | Título corto del error | | `error.message` | string | Descripción del error en español | | `error.details` | string | Detalles técnicos (en inglés) | > [!WARNING] > > Los códigos de error más comunes son `131047` (ventana cerrada), `131051` (número no válido), y `131026` (usuario bloqueó el número). *** ## conversation.unassigned Cuando una conversación es desasignada de un agente. ```json theme={null} { "event": "conversation.unassigned", "timestamp": "2026-01-15T11:00:00.000Z", "businessId": "uuid", "data": { "conversation_id": "conv-uuid", "phone_number": "+5215512345678", "contact_name": "Juan Pérez", "previous_agent": "agent-uuid", "unassigned_by": "admin-uuid", "reason": "manual", "timestamp": "2026-01-15T11:00:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `previous_agent` | string | UUID del agente que tenía la conversación | | `unassigned_by` | string | UUID de quien desasignó (null si fue automático) | | `reason` | string | `manual`, `timeout`, o `reassigned` | *** ## conversation.closed Cuando una conversación es marcada como cerrada. ```json theme={null} { "event": "conversation.closed", "timestamp": "2026-01-15T12:00:00.000Z", "businessId": "uuid", "data": { "conversation_id": "conv-uuid", "phone_number": "+5215512345678", "contact_name": "Juan Pérez", "closed_by": "agent-uuid", "reason": "resolved", "duration_minutes": 45, "message_count": 12, "timestamp": "2026-01-15T12:00:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `closed_by` | string | UUID de quien cerró (null si fue automático) | | `reason` | string | `resolved`, `spam`, `timeout`, `manual` | | `duration_minutes` | number | Duración total de la conversación | | `message_count` | number | Número total de mensajes | *** ## contact.created Cuando se crea un nuevo contacto (primera vez que alguien escribe). ```json theme={null} { "event": "contact.created", "timestamp": "2026-01-15T10:30:00.000Z", "businessId": "uuid", "data": { "contact_id": "contact-uuid", "phone_number": "+5215512345678", "name": "Juan Pérez", "wa_id": "5215512345678", "source": "inbound_message", "timestamp": "2026-01-15T10:30:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `source` | string | `inbound_message`, `import`, `manual` | *** ## contact.updated Cuando se actualizan los datos de un contacto. ```json theme={null} { "event": "contact.updated", "timestamp": "2026-01-15T14:00:00.000Z", "businessId": "uuid", "data": { "contact_id": "contact-uuid", "phone_number": "+5215512345678", "name": "Juan Carlos Pérez", "previous_name": "Juan Pérez", "updated_by": "agent-uuid", "updated_fields": ["name", "email", "tags"], "timestamp": "2026-01-15T14:00:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `updated_by` | string | UUID de quien actualizó | | `updated_fields` | string\[] | Lista de campos modificados | | `previous_name` | string | Nombre anterior (si cambió) | *** ## contact.deleted Cuando se elimina un contacto. ```json theme={null} { "event": "contact.deleted", "timestamp": "2026-01-15T15:00:00.000Z", "businessId": "uuid", "data": { "contact_id": "contact-uuid", "phone_number": "+5215512345678", "name": "Juan Pérez", "deleted_by": "admin-uuid", "reason": "gdpr_request", "timestamp": "2026-01-15T15:00:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `deleted_by` | string | UUID de quien eliminó | | `reason` | string | `gdpr_request`, `spam`, `manual`, `cleanup` | > [!WARNING] > > Al eliminar un contacto, también se eliminan todas sus conversaciones y mensajes. Esta acción es irreversible. *** ## broadcast.sent Cuando un broadcast comienza a enviarse. ```json theme={null} { "event": "broadcast.sent", "timestamp": "2026-01-15T10:00:00.000Z", "businessId": "uuid", "data": { "broadcast_id": "bc-uuid", "name": "Promoción Enero", "template_name": "promo_enero_2026", "total_recipients": 500, "started_at": "2026-01-15T10:00:00.000Z", "created_by": "admin-uuid" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `template_name` | string | Nombre del template usado | | `total_recipients` | number | Número de destinatarios | | `created_by` | string | UUID de quien creó el broadcast | *** ## broadcast.failed Cuando un broadcast falla antes de completarse. ```json theme={null} { "event": "broadcast.failed", "timestamp": "2026-01-15T10:05:00.000Z", "businessId": "uuid", "data": { "broadcast_id": "bc-uuid", "name": "Promoción Enero", "template_name": "promo_enero_2026", "total_recipients": 500, "sent_count": 150, "failed_count": 350, "error": { "code": "rate_limit_exceeded", "message": "Se excedió el límite de mensajes por minuto" }, "failed_at": "2026-01-15T10:05:00.000Z" } } ``` | Campo | Tipo | Descripción | | - | - | - | | `sent_count` | number | Mensajes enviados antes del fallo | | `failed_count` | number | Mensajes que no se enviaron | | `error.code` | string | Código de error | | `error.message` | string | Descripción del error | > [!TIP] > > Si un broadcast falla parcialmente, los mensajes ya enviados no se revierten. Puedes crear un nuevo broadcast con los contactos pendientes. *** ## Próximos Pasos > **[Seguridad](/api-reference/webhooks/security)** > > Verificar la autenticidad de los webhooks > **[Gestión](/api-reference/webhooks/management)** > > Actualizar, probar y eliminar webhooks # Seguridad Source: https://docs.whaapy.com/api-reference/webhooks/security Cómo verificar la autenticidad de los webhooks # Verificación de Firma HMAC Todos los webhooks incluyen una firma HMAC-SHA256 para verificar su autenticidad. **Siempre verifica la firma** antes de procesar un webhook. *** ## Headers de Webhook Cada webhook incluye estos headers: | Header | Descripción | | - | - | | `X-Webhook-Signature` | Firma HMAC-SHA256 del payload | | `X-Webhook-Event` | Tipo de evento (ej: `message.received`) | | `X-Webhook-Timestamp` | Fecha/hora ISO 8601 del envío | | `X-Webhook-ID` | ID único del webhook | *** ## Cómo Funciona 1. Whaapy calcula un HMAC-SHA256 del payload JSON usando tu `secret` 2. El resultado se envía en el header `X-Webhook-Signature` 3. Tú calculas el mismo HMAC con el payload recibido 4. Si coinciden, el webhook es auténtico > [!WARNING] > > **Nunca proceses webhooks sin verificar la firma.** Un atacante podría enviar webhooks falsos a tu endpoint. *** ## Verificación en Node.js ```javascript theme={null} const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } // En tu endpoint Express app.post('/webhook', express.json(), (req, res) => { const signature = req.headers['x-webhook-signature']; const payload = req.body; if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) { console.error('Invalid webhook signature'); return res.status(401).json({ error: 'Invalid signature' }); } // Webhook verificado, procesar... console.log('Webhook received:', req.body.event); res.json({ received: true }); }); ``` > [!TIP] > > Usa `crypto.timingSafeEqual()` para comparar firmas. Esto previene ataques de timing. *** ## Verificación en Python ```python theme={null} import hmac import hashlib import json from flask import Flask, request, jsonify app = Flask(__name__) WEBHOOK_SECRET = "tu_secret_aqui" def verify_webhook_signature(payload: dict, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), json.dumps(payload, separators=(',', ':')).encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) @app.route('/webhook', methods=['POST']) def webhook(): signature = request.headers.get('X-Webhook-Signature') payload = request.get_json() if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET): return jsonify({'error': 'Invalid signature'}), 401 # Webhook verificado, procesar... print(f"Webhook received: {payload['event']}") return jsonify({'received': True}) ``` *** ## Verificación en PHP ```php theme={null} 'Invalid signature']); exit; } // Webhook verificado, procesar... echo json_encode(['received' => true]); ?> ``` *** ## Verificación en Go ```go theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "net/http" ) func verifyWebhookSignature(payload []byte, signature, secret string) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(payload) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } func webhookHandler(w http.ResponseWriter, r *http.Request) { signature := r.Header.Get("X-Webhook-Signature") var payload map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } payloadBytes, _ := json.Marshal(payload) if !verifyWebhookSignature(payloadBytes, signature, webhookSecret) { http.Error(w, "Invalid signature", http.StatusUnauthorized) return } // Webhook verificado, procesar... w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"received": true}`)) } ``` *** ## Regenerar Secret Si crees que tu secret fue comprometido, puedes regenerarlo: ```bash theme={null} curl -X POST https://api.whaapy.com/user-webhooks//regenerate-secret \ -H "Authorization: Bearer wha_TU_API_KEY" ``` > [!WARNING] > > Después de regenerar, actualiza tu código con el nuevo secret. Los webhooks con el secret anterior fallarán la verificación. *** ## Próximos Pasos > **[Gestión de Webhooks](/api-reference/webhooks/management)** > > Actualizar, probar y eliminar webhooks > **[Estructura de Payloads](/api-reference/webhooks/payloads)** > > Ver qué datos recibes en cada evento # Agente IA de Whaapy Source: https://docs.whaapy.com/concepts/ai-agent Cómo funciona el agente de Whaapy en conversaciones reales de WhatsApp # Agente IA de Whaapy El agente IA de Whaapy atiende conversaciones de WhatsApp usando la configuración, conocimiento, herramientas y límites que definas para tu negocio. Su trabajo no es solo contestar mensajes. Su trabajo es sostener una conversación útil sin perder control operativo: responder cuando debe, callarse cuando interviene una persona, usar fuentes correctas y ejecutar acciones solo cuando están habilitadas. > [!TIP] > > Si quieres entender la base del motor nuevo, lee primero [colme-2](/concepts/colme-2). Si quieres configurar el comportamiento, empieza por [Control](/guides/agent-control). *** ## Flujo de una conversación Cuando un cliente escribe por WhatsApp, Whaapy evalúa el estado de la conversación antes de permitir que la IA responda. ```mermaid theme={null} flowchart TD inbound["Mensaje del cliente"] --> control["Revisar Control"] control --> allowed{"La IA puede responder"} allowed -->|"No"| stopNode["No responder"] allowed -->|"Si"| context["Preparar contexto"] context --> knowledge["Consultar Knowledge"] context --> tools["Usar Tools si hace falta"] context --> subagents["Delegar a Subagents si aplica"] tools --> response["Generar respuesta"] knowledge --> response subagents --> response response --> whatsapp["Enviar por WhatsApp"] ``` Este flujo evita que el agente responda a ciegas. Primero revisa si está habilitado, si hay una pausa activa, si un humano tomó control o si la conversación tiene reglas particulares. *** ## Qué configura el comportamiento El agente no depende de una sola caja de texto. Está compuesto por varias capas: | Capa | Qué define | | - | - | | `Control` | Cuándo responde, cuándo se pausa, cómo hace handoff y cómo maneja respuestas largas | | `Knowledge` | Qué sabe del negocio: documentos, políticas, FAQs, servicios, precios y procesos | | `Tools` | Qué acciones puede ejecutar o consultar dentro de Whaapy | | `Subagents` | Qué tareas especializadas puede delegar a agentes más enfocados | | `MCPs` | Qué sistemas externos puede conectar como capacidades estructuradas | Separar estas capas hace que el agente sea más fácil de mantener. El prompt principal no tiene que cargar con todo. *** ## Qué pasa con varios mensajes seguidos En WhatsApp, una persona puede escribir así: ```text theme={null} Hola Quiero una cita El martes si se puede En la tarde ``` Si la IA respondiera a cada línea, la experiencia sería torpe. Con `colme-2`, puedes configurar batching para que el agente espere una ventana corta y responda al bloque completo. Eso mejora tres cosas: * evita respuestas prematuras * reduce ruido en la conversación * permite entender mejor la intención completa del cliente > [!NOTE] > > El batching puede agregar una pequeña espera antes de la respuesta. Es normal: el objetivo es responder mejor, no contestar a mitad de una idea. *** ## Cómo convive con humanos El agente debe poder operar junto a tu equipo. Casos comunes: * un vendedor toma la conversación * soporte necesita revisar un caso sensible * un flujo externo envía una actualización * el cliente pide hablar con una persona * una automatización interna o externa responde desde Whaapy, n8n, Zapier o tu CRM Para eso existen controles de pausa, apagado por conversación, handoff y control desde la API. > **Pausa temporal** > > La IA deja de responder durante un tiempo y se reactiva después. > **Apagado por conversación** > > La IA queda desactivada para una conversación específica. > **Handoff** > > La conversación pasa a una persona o equipo humano. > **Control por API** > > Tus integraciones pueden pausar o desactivar la IA. Ver [Agente IA](/api-reference/agent/overview) y [Control por Conversación](/api-reference/conversations/ai-control) para hacerlo desde API. Si quieres coordinar reglas del dashboard con la IA, revisa [Automations e IA](/guides/automations/ai-handoff). *** ## Memoria y conocimiento El agente puede usar dos tipos de contexto: * **Contexto conversacional**: lo que ha pasado en la conversación. * **Knowledge del negocio**: documentos, instrucciones, FAQs, catálogos simples y políticas que tú le das. No conviene meter todo en el prompt principal. Usa `Knowledge` para información que el agente debe consultar y usa `Control` para reglas operativas. Ejemplos de buena knowledge: * horarios de atención * políticas de cambios y devoluciones * servicios ofrecidos * preguntas frecuentes * instrucciones de atención * documentos comerciales * condiciones por sucursal o zona Ejemplos que normalmente convienen como tool o integración: * inventarios vivos * disponibilidad de agenda en tiempo real * estados de pedido * precios que cambian cada hora * datos privados de clientes *** ## Qué esperar del agente El agente puede hacer mucho, pero sigue siendo una automatización configurada por tu negocio. | Espera esto | No esperes esto | | - | - | | Respuestas consistentes basadas en tu configuración | Que adivine políticas que no documentaste | | Uso de tools habilitadas | Acceso automático a sistemas no conectados | | Pausas y handoff según reglas | Que ignore siempre al humano sin configuración | | Mejor manejo de conversaciones largas | Memoria perfecta e infinita de todo | | Acciones controladas | Permisos ilimitados sobre tu operación | La calidad del agente depende de tres cosas: configuración clara, knowledge limpia y tools bien acotadas. *** ## Siguiente paso Configura el agente en este orden: **1. Control** Define cuándo puede responder la IA y cómo se comporta con humanos. Ver [Control](/guides/agent-control). **2. Knowledge** Sube información útil y mantenible para que el agente responda con contexto. Ver [Knowledge](/guides/agent-knowledge). **3. Tools y MCPs** Conecta acciones o sistemas externos solo cuando el agente necesite operar, no solo conversar. Ver [Tools](/guides/agent-tools) y [MCPs](/guides/agent-mcps). **4. Subagents** Divide tareas complejas en subagentes especializados cuando el prompt principal empiece a crecer demasiado. Ver [Subagents](/guides/agent-subagents). # Automatizaciones Source: https://docs.whaapy.com/concepts/automations Qué son las automatizaciones de Whaapy y cuándo usarlas para operar conversaciones de WhatsApp # Automatizaciones Las automatizaciones de Whaapy te permiten crear reglas tipo **si pasa esto, haz aquello** dentro de WhatsApp. Sirven para ejecutar acciones repetibles sin depender de que una persona o una integración externa revise cada conversación manualmente. > [!NOTE] > > Las automatizaciones viven en el dashboard de Whaapy en `/automations`. No necesitas llamar al motor interno ni configurar infraestructura para usarlas. *** ## Qué resuelven En un negocio real, muchas acciones se repiten todos los días: * etiquetar contactos nuevos * enviar un mensaje de bienvenida * mover un lead a una etapa del funnel * pausar la IA cuando interviene un humano * asignar conversaciones a agentes * avisar a un CRM o sistema externo * responder fuera de horario * procesar leads de Meta Lead Ads Una automatización convierte esas reglas en un flujo explícito, visible y editable. ```mermaid theme={null} flowchart LR eventNode["Evento en Whaapy"] --> triggerNode["Trigger coincide"] triggerNode --> optionsNode["Opciones avanzadas"] optionsNode --> actionsNode["Acciones en orden"] actionsNode --> resultNode["Resultado para cliente o equipo"] actionsNode --> aiNode["Control de IA"] actionsNode --> webhookNode["HTTP request o n8n"] ``` *** ## Modelo mental Cada automatización tiene cuatro partes: | Parte | Qué define | | - | - | | Nombre y descripción | Para qué existe la regla | | Trigger | Qué evento o condición la dispara | | Opciones avanzadas | Prioridad, ejecución única, omitir IA o parar otras reglas | | Acciones | Qué debe pasar cuando coincide | Ejemplo: ```text theme={null} Trigger: Cliente escribe "quiero cotizar" Acciones: 1. Agregar tag "lead-caliente" 2. Mover contacto a etapa "Cotización" 3. Enviar mensaje de bienvenida 4. Asignar agente por round robin ``` *** ## Cuándo usar Automations Usa automatizaciones cuando quieres que Whaapy actúe sobre eventos concretos. > **Reglas repetibles** > > Etiquetar, mover, asignar o enviar mensajes cuando ocurre algo específico. > **Operación comercial** > > Clasificar leads, avanzar funnels y dar seguimiento sin hacerlo manualmente. > **Control de IA** > > Pausar, apagar o coordinar la IA para evitar dobles respuestas. > **Sistemas externos** > > Enviar datos a otros sistemas usando HTTP requests o n8n. *** ## Automations vs IA vs Webhooks vs n8n | Necesidad | Usa | | - | - | | Responder con criterio conversacional | [Agente IA](/concepts/ai-agent) | | Ejecutar una regla simple dentro de Whaapy | Automatizaciones | | Recibir eventos en tu sistema | [Webhooks](/api-reference/webhooks/overview) | | Orquestar flujos complejos entre apps | [n8n](/integrations/n8n) | | Mandar mensajes desde tu app | [Messages API](/api-reference/messages/send) | No compiten entre sí. Normalmente se combinan. Ejemplo: * una automatización detecta un lead nuevo * actualiza el contacto * asigna un agente * envía un template * pausa la IA * avisa a n8n o a tu CRM *** ## Qué puedes automatizar ### Triggers Una automatización puede dispararse por texto, comportamiento del cliente, cambios de funnel, horarios, eventos de IA o Meta Lead Ads. > **Texto del cliente** > > Coincidencia exacta, contiene texto, keywords o regex. > **Eventos de conversación** > > Primer mensaje, respuesta a IA o acciones posteriores al agente. > **Funnel y horarios** > > Entrada a una etapa del pipeline o rango horario. > **Meta Lead Ads** > > Lead recibido desde formularios conectados de Meta. Ver [Triggers](/guides/automations/triggers). ### Acciones Las acciones se ejecutan en orden. Pueden agregar tags, actualizar campos, mover etapas, enviar mensajes o templates, controlar IA, llamar un webhook, asignar agentes y actualizar datos del contacto. Ver [Actions](/guides/automations/actions). *** ## Opciones avanzadas | Opción | Para qué sirve | | - | - | | Prioridad | Decide qué regla se evalúa antes cuando hay varias posibles | | Ejecutar una vez | Evita repetir la misma regla para el mismo contexto | | Omitir IA | Evita que el agente responda cuando la automatización ya se encargó | | Parar al coincidir | Hace que esta regla sea exclusiva y detenga otras automatizaciones compatibles | Estas opciones son importantes cuando tienes varias reglas parecidas. > [!WARNING] > > Si dos automatizaciones responden al mismo mensaje y ninguna detiene a la otra, puedes terminar con acciones duplicadas. *** ## Casos comunes > **[Bienvenida a nuevos contactos](/guides/automations/getting-started)** > > Detecta primer mensaje, etiqueta al contacto y envía respuesta inicial. > **[Meta Lead Ads](/guides/automations/meta-leads)** > > Convierte leads de formularios en contactos accionables. > **[Pausa de IA al escalar](/guides/automations/ai-handoff)** > > Evita que la IA conteste cuando tu equipo toma control. > **[Webhook a CRM](/guides/automations/webhooks-and-n8n)** > > Manda datos a sistemas externos o workflows de n8n. *** ## Por dónde empezar **1. Crea una automatización simple** Usa un trigger fácil de probar, como `contains` o `keywords`. Ver [Primer flujo](/guides/automations/getting-started). **2. Agrega una acción segura** Empieza con tag, custom field o mensaje interno antes de usar HTTP requests o asignación. **3. Prueba antes de activar** Usa el test del builder para confirmar que el trigger coincide como esperas. **4. Activa y observa** Déjala activa y revisa resultados antes de agregar más reglas parecidas. # colme-2 Source: https://docs.whaapy.com/concepts/colme-2 La nueva generación del agente de Whaapy para conversaciones, operaciones y automatización en WhatsApp # colme-2 `colme-2` es la nueva generación del agente de Whaapy. No es una pantalla nueva ni un endpoint adicional. Es la base que permite que el agente responda, recuerde, use herramientas, escale a humanos y opere dentro de WhatsApp con más control. > [!NOTE] > > `colme-2` corre detrás de Whaapy. Tú sigues usando el dashboard, la API pública en `https://api.whaapy.com` y los mismos flujos de WhatsApp. *** ## Por qué existe Los agentes de negocio no fallan solo porque el modelo "no sepa". Fallan cuando el sistema le pide al modelo que recuerde demasiadas reglas a la vez: * cuándo responder y cuándo callarse * cuándo transferir a un humano * qué datos puede usar * qué acciones puede ejecutar * cómo manejar varios mensajes seguidos * cómo evitar respuestas enormes o fuera de contexto * cómo mantener una conversación larga sin perder el hilo `colme-1` permitió crear la primera generación de agentes en Whaapy. `colme-2` existe porque la siguiente etapa necesitaba menos dependencia de prompts gigantes y más comportamiento controlado por producto. En términos simples: `colme-2` mueve decisiones críticas del texto del prompt al sistema que ejecuta el agente. *** ## Qué resuelve > **Control real** > > La IA puede activarse, pausarse, apagarse o ceder la conversación a humanos sin depender solo de instrucciones escritas. > **Mejor contexto** > > El agente puede usar memoria, knowledge base y mensajes recientes sin cargar toda la conversación de forma ingenua. > **Herramientas operativas** > > El agente puede apoyarse en tools para consultar datos, ejecutar acciones o disparar automatizaciones del negocio. > **Aislamiento por negocio** > > Cada negocio tiene su configuración, conocimiento, permisos y comportamiento sin mezclarse con otros negocios. > **Handoff más claro** > > Cuando un humano toma el control, la IA puede detenerse o quedar en pausa para evitar dobles respuestas. > **Base para escalar** > > La arquitectura permite crecer en volumen, herramientas, subagentes y conectores sin rehacer el agente desde cero. *** ## Cómo se ve desde fuera Desde el punto de vista del usuario, el flujo sigue siendo simple: ```mermaid theme={null} flowchart LR customer["Cliente en WhatsApp"] --> whatsapp["WhatsApp"] whatsapp --> whaapy["Whaapy"] whaapy --> agent["Agente colme-2"] agent --> knowledge["Knowledge"] agent --> tools["Tools"] agent --> subagents["Subagents"] agent --> mcps["MCPs"] agent --> human["Equipo humano"] agent --> whatsapp ``` El cliente escribe por WhatsApp. Whaapy recibe el mensaje, decide si la IA debe responder, prepara el contexto correcto y deja que el agente actúe con las capacidades configuradas para ese negocio. Si el agente necesita información, consulta `Knowledge`. Si necesita ejecutar una acción, usa `Tools` o `MCPs`. Si el caso requiere una responsabilidad especializada, puede apoyarse en `Subagents`. Si debe intervenir una persona, se transfiere o pausa la IA según la configuración. *** ## Qué cambia para el usuario ### Menos prompt magic Antes era común meter demasiadas reglas en el prompt principal: ```text theme={null} Si el cliente pide humano, transfiere. Si pregunta precio, busca catálogo. Si manda varios mensajes, espera. Si no sabes, no inventes. Si... ``` Eso escala mal. El prompt termina mezclando tono, políticas, conocimiento, herramientas y control operativo. Con `colme-2`, cada cosa vive donde corresponde: | Necesidad | Dónde se configura | | - | - | | Cuándo responde la IA | `Control` | | Qué sabe del negocio | `Knowledge` | | Qué puede hacer | `Tools` | | Qué tareas delega | `Subagents` | | Qué sistemas externos conecta | `MCPs` | ### Mejor convivencia con humanos La IA no debería pelear con tu equipo. `colme-2` está pensado para que un humano pueda intervenir sin que el agente siga contestando encima. Puedes configurar pausas, apagado por conversación, keywords de handoff y reglas para desactivar la IA cuando una persona toma control. ### Mejor manejo de conversaciones reales En WhatsApp la gente no escribe en un solo mensaje perfecto. Manda frases cortas, corrige, agrega contexto y cambia de idea. `colme-2` está diseñado para trabajar con esa realidad: * puede agrupar mensajes cercanos antes de responder * puede partir respuestas largas * puede respetar pausas humanas * puede usar memoria conversacional * puede operar con conocimiento y herramientas sin meter todo en un prompt *** ## Qué no es colme-2 > [!WARNING] > > `colme-2` no es una API pública nueva. No debes llamar directo al motor interno ni configurar infraestructura para usarlo. `colme-2` tampoco reemplaza los endpoints de mensajes. Si integras Whaapy por API, sigues usando la referencia pública de mensajes, media, webhooks y control de IA. Lo que cambia es la capacidad del agente que vive dentro de Whaapy. *** ## Cómo configurarlo Empieza por estas guías: > **[Control](/guides/agent-control)** > > Define cuándo responde la IA, cuándo se pausa y cómo convive con humanos. > **[Knowledge](/guides/agent-knowledge)** > > Alimenta al agente con documentos, FAQs, políticas y contexto del negocio. > **[Tools](/guides/agent-tools)** > > Habilita acciones reales como agenda, catálogo, triggers y handoff. > **[Subagents](/guides/agent-subagents)** > > Divide responsabilidades en agentes especializados por tarea. > **[MCPs](/guides/agent-mcps)** > > Conecta sistemas externos como fuentes o acciones controladas. > **[Control vía API](/api-reference/agent/overview)** > > Pausa o desactiva la IA desde tus integraciones. *** ## La idea central `colme-2` convierte al agente de Whaapy en algo más cercano a una capa operativa del negocio: * conversa con clientes * entiende reglas del negocio * usa conocimiento actualizado * ejecuta acciones permitidas * escala a humanos cuando toca * convive con integraciones externas El objetivo no es que la IA "suene inteligente". El objetivo es que sea útil, controlable y confiable dentro de conversaciones reales de WhatsApp. # Subagents y Tools Source: https://docs.whaapy.com/concepts/subagents-and-tools Cómo pensar capacidades especializadas, acciones y conectores dentro del agente de Whaapy # Subagents y Tools Un agente útil no debería resolver todo con un solo prompt. En Whaapy, `Tools`, `Subagents` y `MCPs` son la forma de darle capacidades concretas al agente sin volverlo una mezcla inmanejable de reglas. > [!NOTE] > > Piensa en el agente principal como el coordinador de la conversación. Las tools ejecutan acciones, los subagents se enfocan en tareas específicas y los MCPs conectan sistemas externos. *** ## Diferencia rápida | Elemento | Qué es | Cuándo usarlo | | - | - | - | | `Tool` | Una acción o consulta concreta | Agendar, buscar catálogo, disparar un trigger, transferir a humano | | `Subagent` | Un agente especializado en una responsabilidad | Ventas, soporte, agenda, calificación, seguimiento | | `MCP` | Un conector estructurado a un sistema externo | CRM, base de datos, calendario, docs, herramientas internas | | `Knowledge` | Información que el agente consulta para responder | FAQs, políticas, servicios, procesos, documentos | La regla práctica: * si el agente necesita **saber**, usa `Knowledge` * si necesita **hacer una acción puntual**, usa `Tools` * si necesita **trabajar como especialista**, usa `Subagents` * si necesita **conectarse a otro sistema**, usa `MCPs` *** ## Tools Una tool convierte una intención del cliente en una acción controlada. Ejemplos: * consultar disponibilidad * agendar una cita * cancelar una cita * buscar un producto * disparar un flujo externo * transferir a humano * crear una nota interna La diferencia importante: una tool no es "la IA prometiendo que hizo algo". Una tool es una capacidad configurada para que el agente pueda consultar o ejecutar algo de forma explícita. ### Buen uso ```text theme={null} Cliente: "Tienes espacio el viernes?" Agente: consulta disponibilidad con una tool. Agente: responde con horarios reales. ``` ### Mal uso ```text theme={null} Cliente: "Agenda para el viernes." Agente: responde "listo" sin usar ninguna tool de agenda. ``` Si una acción cambia algo en el negocio, debería estar respaldada por una tool o un MCP, no solo por texto. *** ## Subagents Un subagent es una especialización del agente principal. Sirve cuando una parte del flujo necesita instrucciones, objetivo o contexto propio. Ejemplos: > **Ventas** > > Califica intención, recomienda productos y empuja al siguiente paso comercial. > **Soporte** > > Pide datos mínimos, identifica problema y decide si debe escalar. > **Agenda** > > Coordina disponibilidad, confirma horarios y evita dobles reservas. > **Seguimiento** > > Reactiva conversaciones abandonadas con reglas claras. No crees subagents para todo. Úsalos cuando separar responsabilidades reduzca complejidad. > [!TIP] > > Si una regla cabe en una frase, probablemente va en `Control` o en el prompt. Si requiere un flujo completo, probablemente merece un subagent. *** ## MCPs MCP significa Model Context Protocol. En Whaapy puedes pensarlo como una forma estándar de conectar capacidades externas al agente. No necesitas verlo como infraestructura. Para el usuario, un MCP es un conector que responde preguntas como: * qué sistema puede consultar el agente * qué acciones puede ejecutar * con qué permisos * qué datos quedan disponibles * cómo se prueba o desconecta Ejemplos de uso: * consultar un CRM * leer documentación interna * buscar datos en una base externa * consultar disponibilidad en un sistema tercero * ejecutar acciones en una herramienta operativa *** ## Cómo trabajan juntos ```mermaid theme={null} flowchart TD mainAgent["Agente principal"] --> sales["Subagent de ventas"] mainAgent --> support["Subagent de soporte"] mainAgent --> agenda["Subagent de agenda"] mainAgent --> tools["Tools"] mainAgent --> mcps["MCPs"] tools --> actions["Acciones controladas"] mcps --> externalSystems["Sistemas externos"] ``` Un flujo real puede verse así: **1. El cliente escribe** "Quiero cotizar y saber si pueden instalar esta semana." **2. El agente principal entiende intención** Detecta que hay una parte comercial y una parte operativa. **3. Un subagent de ventas califica** Pide datos mínimos y entiende presupuesto, zona o urgencia. **4. Una tool consulta disponibilidad** El agente no inventa horarios: consulta una capacidad conectada. **5. El agente responde** Da una respuesta clara y, si aplica, agenda o transfiere a humano. *** ## Buenas prácticas * Crea tools para acciones que tengan efecto real. * Crea subagents para responsabilidades completas, no para reglas pequeñas. * Usa MCPs cuando la fuente o acción vive fuera de Whaapy. * Define límites claros: qué puede hacer, qué no puede hacer y cuándo escalar. * Prueba cada capacidad con conversaciones reales antes de activarla en producción. * Mantén el prompt principal corto; delega conocimiento a `Knowledge` y acciones a `Tools`. *** ## Configuración relacionada > **[Tools](/guides/agent-tools)** > > Configura acciones disponibles para el agente. > **[Subagents](/guides/agent-subagents)** > > Diseña agentes especializados por tarea o flujo. > **[MCPs](/guides/agent-mcps)** > > Conecta sistemas externos como capacidades estructuradas. > **[Knowledge](/guides/agent-knowledge)** > > Alimenta el contexto que los agentes usan para responder. # Configurar Control Source: https://docs.whaapy.com/guides/agent-control Cómo configurar la tab Control del agente IA de Whaapy # Configurar Control > [!TIP] > > Míralo en video: **Agente IA completo** (20 min). [Todos los videos de Agente IA](/tutorials/agente-ia). La tab `Control` define cuándo puede responder la IA, cuándo debe detenerse y cómo convive con tu equipo humano. Configura esto antes de poner el agente en producción. Si `Control` está mal definido, el agente puede responder cuando no debe, interrumpir a humanos o contestar antes de que el cliente termine de escribir. *** ## Qué se configura aquí | Configuración | Para qué sirve | | - | - | | Motor del agente | Elegir la generación del agente, por ejemplo `colme-1` o `colme-2` cuando esté disponible | | IA activa | Encender o apagar la IA para el negocio | | Pausa automática | Detener la IA después de una intervención humana o externa | | Batching de mensajes | Esperar varios mensajes cercanos antes de responder | | Memoria conversacional | Permitir que el agente use contexto reciente de la conversación | | Handoff | Transferir a humano cuando el caso lo requiere | | Keywords de escalación | Detectar frases como "quiero hablar con alguien" | | Split de respuestas largas | Partir respuestas extensas en varios mensajes de WhatsApp | > [!NOTE] > > `Control` no define lo que el agente sabe. Eso va en [Knowledge](/guides/agent-knowledge). Tampoco define acciones externas. Eso va en [Tools](/guides/agent-tools) o [MCPs](/guides/agent-mcps). *** ## Configuración recomendada **1. Elige el motor** Usa `colme-2` para nuevos agentes o para negocios donde necesitas mejor control de tools, handoff, batching y memoria. **2. Activa la IA** Enciende la IA solo cuando ya tengas prompt, knowledge y reglas mínimas de handoff. **3. Configura batching** Activa batching si tus clientes suelen mandar varios mensajes seguidos. Una ventana corta evita que el agente responda a mitad de la idea. **4. Define pausa humana** Activa pausa automática cuando tu equipo responda manualmente. Esto evita dobles respuestas entre humano e IA. **5. Configura handoff** Activa handoff si hay casos que deben pasar a ventas, soporte, cobranza o una persona específica. **6. Prueba respuestas largas** Activa split de respuestas largas si el agente suele explicar procesos, cotizaciones o instrucciones extensas. *** ## Motor del agente | Motor | Cuándo usarlo | | - | - | | `colme-1` | Flujos legacy que todavía no quieres migrar | | `colme-2` | Nuevos agentes, uso de tools, subagents, MCPs, batching y handoff más robusto | > [!TIP] > > Si estás configurando un agente desde cero, empieza con `colme-2`. *** ## Activar o apagar la IA La activación global decide si el agente puede responder en el negocio. * **Activo**: el agente puede responder según las reglas de cada conversación. * **Inactivo**: la IA no responde en ninguna conversación del negocio. Apagar la IA globalmente es útil para mantenimiento, revisión de configuración o incidentes operativos. Si solo quieres detener una conversación específica, usa pausa o apagado por conversación desde el inbox o la API de [Control por Conversación](/api-reference/conversations/ai-control). *** ## Pausa automática La pausa automática evita que la IA conteste encima de un humano. Actívala cuando: * tu equipo responde desde el inbox * una integración externa manda mensajes * hay procesos que requieren revisión manual * el cliente pidió hablar con una persona Recomendación inicial: | Tipo de negocio | Duración sugerida | | - | - | | Soporte rápido | 15 a 30 minutos | | Ventas consultivas | 30 a 120 minutos | | Casos sensibles | 4 a 24 horas | > [!WARNING] > > No uses pausas enormes como sustituto de apagar la IA. Si un flujo siempre debe ser manual, desactiva la IA en esa conversación o configura handoff. *** ## Batching de mensajes El batching agrupa mensajes cercanos antes de que el agente responda. ```text theme={null} Cliente: Hola Cliente: quiero una cita Cliente: para el viernes Cliente: en la tarde ``` Sin batching, la IA puede responder demasiado pronto. Con batching, el agente espera una ventana breve y entiende el bloque completo. | Campo | Recomendación | | - | - | | Activar batching | Sí, para WhatsApp casi siempre conviene | | Ventana | 3 a 8 segundos | | Máximo de mensajes | 3 a 6 mensajes | Usa ventanas más cortas para soporte urgente y más largas para ventas o agendamiento. *** ## Memoria conversacional La memoria permite que el agente use contexto reciente para mantener continuidad. Actívala cuando: * el cliente vuelve a preguntar sobre lo mismo * el flujo tiene varios pasos * la conversación incluye preferencias o datos previos * el agente necesita entender el historial reciente No la uses para guardar datos sensibles que deberían vivir en un sistema transaccional. Para datos operativos, usa `Tools` o `MCPs`. *** ## Handoff y keywords El handoff define cuándo la conversación debe pasar a una persona. Keywords útiles: ```text theme={null} humano asesor agente persona real quiero hablar con alguien llámame queja cancelar servicio ``` Buenas prácticas: * incluye variantes reales que usan tus clientes * no metas keywords demasiado genéricas como "ayuda" * combina keywords con reglas del subagent de soporte si el flujo es complejo * activa `desactivar IA al transferir` si tu equipo tomará el caso manualmente *** ## Split de respuestas largas WhatsApp se siente mejor cuando las respuestas largas se parten en bloques legibles. Activa split si el agente responde instrucciones paso a paso, cotizaciones, políticas extensas, listas de productos o explicaciones de procesos. | Campo | Valor sugerido | | - | - | | Split activo | Sí | | Umbral | 800 a 1500 caracteres | *** ## Errores comunes | Error | Qué pasa | Cómo corregirlo | | - | - | - | | Activar IA sin knowledge | El agente responde genérico | Configura [Knowledge](/guides/agent-knowledge) antes de producción | | Batching apagado | Responde antes de que el cliente termine | Activa batching con ventana corta | | Sin pausa humana | Humano e IA contestan al mismo tiempo | Activa pausa automática | | Keywords genéricas | Escala demasiadas conversaciones | Usa frases específicas | | No partir respuestas largas | Mensajes pesados y difíciles de leer | Activa split | *** ## API relacionada > **[Agente IA](/api-reference/agent/overview)** > > Endpoints globales para activar, pausar y generar sugerencias. > **[Control por Conversación](/api-reference/conversations/ai-control)** > > Pausa o desactiva la IA en conversaciones específicas. # Configurar Knowledge Source: https://docs.whaapy.com/guides/agent-knowledge Cómo alimentar al agente con documentos, políticas, FAQs y contexto del negocio # Configurar Knowledge La tab `Knowledge` contiene la información que el agente puede consultar para responder con contexto del negocio. Úsala para sacar información estable del prompt principal y convertirla en una base consultable: servicios, políticas, FAQs, procesos, precios simples, condiciones, cobertura y documentos comerciales. *** ## Qué debe ir en Knowledge > **FAQs** > > Preguntas frecuentes, respuestas aprobadas y dudas comunes. > **Políticas** > > Cambios, devoluciones, garantías, cancelaciones y términos de servicio. > **Servicios** > > Qué ofrece el negocio, cómo se vende, duración, requisitos y restricciones. > **Procesos** > > Pasos para cotizar, agendar, comprar, renovar, reclamar o recibir soporte. > **Catálogos simples** > > Listas pequeñas o medianas de productos con información estable. > **Contexto de marca** > > Tono, estilo, promesas permitidas y cosas que el agente debe evitar. > [!WARNING] > > No uses Knowledge como base de datos viva. Inventario en tiempo real, pedidos, disponibilidad o datos privados de clientes deben ir por [Tools](/guides/agent-tools) o [MCPs](/guides/agent-mcps). *** ## Cómo subir documentos **1. Prepara el documento** Divide la información por tema. Un documento claro sobre "política de cambios" funciona mejor que un PDF enorme con todo mezclado. **2. Ponle un nombre descriptivo** Usa nombres como `Politica de cambios`, `Servicios dentales`, `Preguntas frecuentes de envios` o `Catalogo basico 2026`. **3. Agrega descripción de uso** Explica cuándo debe usarlo el agente. Por ejemplo: "Usar cuando el cliente pregunte por envíos, tiempos de entrega o zonas de cobertura". **4. Sube el archivo** Carga el documento desde la tab `Knowledge` o desde SDK/API si tu integración lo permite. **5. Prueba con preguntas reales** Haz preguntas como las haría un cliente. Ajusta el documento si el agente responde ambiguo. *** ## Formato recomendado Los mejores documentos son directos, estructurados y explícitos. ### Bueno ```md theme={null} # Politica de cambios Los cambios se aceptan dentro de los primeros 15 dias despues de la compra. El producto debe estar sin uso y con empaque original. No aplican cambios en productos personalizados. Si el cliente quiere hacer un cambio: 1. Pedir numero de pedido. 2. Confirmar fecha de compra. 3. Explicar que soporte revisara el caso. ``` ### Malo ```md theme={null} Somos una empresa comprometida con la satisfaccion del cliente. Dependiendo del caso se puede revisar la situacion y buscar alternativas. ``` El segundo ejemplo suena bien, pero no le da reglas operativas al agente. *** ## Descripciones útiles | Documento | Descripción útil | | - | - | | `Horarios` | Usar cuando pregunten por horarios, dias festivos o disponibilidad general | | `Precios` | Usar cuando pregunten por costos aproximados, paquetes o planes | | `Politica de garantia` | Usar cuando el cliente mencione fallas, cambios, devoluciones o garantia | | `Servicios` | Usar para explicar servicios, requisitos, duracion y beneficios | Evita descripciones como: ```text theme={null} Documento importante Info general Usar siempre ``` *** ## Cuándo actualizar Knowledge Actualiza Knowledge cuando cambie algo que el agente pueda prometer: * precios * horarios * cobertura * requisitos * políticas * servicios activos * tiempos de entrega * condiciones legales o comerciales > [!TIP] > > Si una respuesta incorrecta puede causar costo, promesa falsa o mala experiencia, esa información debe estar documentada y actualizada. *** ## Knowledge vs Prompt vs Tools | Necesidad | Mejor lugar | | - | - | | Tono del agente | Prompt o configuración general | | Reglas de cuándo responder | `Control` | | FAQs y políticas | `Knowledge` | | Inventario actualizado | `Tools` o `MCPs` | | Consultar pedido de cliente | `Tools` o `MCPs` | | Agendar cita | `Tools` | | Flujo especializado de ventas | `Subagents` | *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Subir un PDF enorme sin estructura | Respuestas ambiguas | Divide por tema | | No poner descripción | El agente no sabe cuándo usarlo | Describe el caso de uso | | Meter datos vivos | Respuestas desactualizadas | Usa Tools o MCPs | | Contradecir el prompt | Comportamiento inconsistente | Alinea prompt y documentos | | No probar preguntas reales | Falsos positivos | Prueba con conversaciones reales | *** ## Siguiente paso Cuando Knowledge ya esté limpia, conecta acciones: > **[Tools](/guides/agent-tools)** > > Permite que el agente consulte o ejecute acciones reales. > **[Subagents](/guides/agent-subagents)** > > Separa ventas, soporte, agenda o seguimiento en flujos especializados. # Configurar MCPs Source: https://docs.whaapy.com/guides/agent-mcps Cómo conectar sistemas externos al agente de Whaapy mediante MCPs # Configurar MCPs La tab `MCPs` conecta al agente con sistemas externos mediante capacidades estructuradas. MCP significa Model Context Protocol. Para usar Whaapy no necesitas aprender el protocolo a bajo nivel. En la práctica, un MCP es un conector que le da al agente acceso controlado a datos o acciones fuera de Whaapy. > [!TIP] > > Si vas a construir tu propio servidor MCP para Whaapy, revisa la [especificación técnica de compatibilidad](/guides/mcp-server-spec). *** ## Cuándo usar MCPs Usa MCPs cuando el agente necesita trabajar con sistemas externos: * CRM * calendario externo * base de datos * documentos internos * ERP * helpdesk * e-commerce * herramientas propias del negocio * automatizaciones internas Si la información vive dentro de un documento estable, usa [Knowledge](/guides/agent-knowledge). Si la acción ya existe como capacidad nativa de Whaapy, usa [Tools](/guides/agent-tools). Si viene de otro sistema, usa `MCPs`. *** ## Qué puede aportar un MCP > **Lectura de datos** > > Consultar registros, pedidos, clientes, tickets, inventario o documentación. > **Acciones externas** > > Crear tickets, actualizar campos, agendar eventos o disparar procesos. > **Contexto especializado** > > Darle al agente acceso a conocimiento que no vive en Whaapy. > **Integración controlada** > > Limitar qué puede ver o ejecutar el agente. *** ## Cómo configurar un MCP **1. Elige el sistema** Define qué sistema quieres conectar y qué problema resuelve para el agente. **2. Agrega el MCP** Crea o selecciona el conector desde la tab `MCPs`. **3. Configura credenciales** Agrega API keys, tokens u otro método de autenticación requerido por el conector. **4. Selecciona capacidades** Habilita solo las herramientas que el agente debe usar. No expongas todo el sistema. **5. Mapea campos de identidad** En cada tool activa, abre **Configurar tool** y vincula campos como teléfono o email a variables de Whaapy. Así el agente no inventa ni pide datos que ya tiene el contacto. **6. Define permisos** Separa lectura y escritura. Leer datos suele ser menos riesgoso que modificar registros. **7. Prueba conexión** Ejecuta una prueba simple antes de usarlo en una conversación real. **8. Conecta con Tools o Subagents** Decide qué tool o subagent puede usar ese MCP y bajo qué condiciones. *** ## Campos de una tool MCP Cada tool remota declara los campos que necesita (por ejemplo `phone`, `waId`, `sku` o `email`). En una tool activa, el lápiz abre **Configurar tool**: instrucciones para la IA y una fila por cada campo del schema. | Valor del campo | Qué hace Whaapy | | - | - | | Vacío | La IA extrae el dato de la conversación y lo envía a la tool | | Variable, por ejemplo `{{contact.phoneNumber}}` | Whaapy lo inyecta siempre. La IA ya no pide ese dato | | Texto fijo, por ejemplo `true` | Se envía tal cual en cada llamada | El selector de variables es el mismo que en Tools HTTP. También hay chips rápidos para teléfono, nombre y email. Ejemplos típicos: | Campo que pide la tool | Valor recomendado | | - | - | | `phone`, `waId`, `phoneNumber` | `{{contact.phoneNumber}}` | | `email` | `{{contact.email}}` | | `sku`, `query`, `qty` | Vacío, para que la IA lo tome de la conversación | > [!NOTE] > > El catálogo completo de variables (`contact.*`, `business.*`, `system.*`) está en [Variables](/guides/automations/variables). > [!WARNING] > > Si la variable no tiene valor y el campo es requerido, la tool falla. No dejes que la IA invente un teléfono: mapea el campo o pide el dato al cliente antes de ejecutar. *** ## Permisos recomendados Empieza con el menor permiso posible. | Necesidad | Permiso recomendado | | - | - | | Responder dudas desde docs externas | Solo lectura | | Consultar estado de pedido | Solo lectura por ID o cliente | | Crear ticket | Escritura limitada a tickets | | Actualizar CRM | Escritura limitada a campos específicos | | Cancelar o cobrar | Requiere confirmación y normalmente humano | > [!WARNING] > > No conectes credenciales con permisos administrativos si el agente solo necesita consultar datos. *** ## Ejemplo: CRM ```text theme={null} Sistema: CRM Objetivo: Permitir que el agente consulte información básica del lead y cree una nota de seguimiento. Capacidades: - Buscar lead por teléfono - Leer estado del lead - Crear nota No permitido: - Borrar leads - Cambiar dueño del lead - Modificar etapa de venta sin confirmación ``` *** ## Ejemplo: helpdesk ```text theme={null} Sistema: Helpdesk Objetivo: Permitir que el subagent de soporte cree tickets cuando no puede resolver el caso. Capacidades: - Crear ticket - Adjuntar resumen de conversación - Consultar estado de ticket Reglas: - Pedir correo o número de pedido antes de crear ticket. - Si el cliente está molesto, transferir a humano después de crear ticket. ``` *** ## MCPs y seguridad operativa Un MCP amplía lo que el agente puede hacer. Eso es poderoso, pero exige límites claros. Checklist antes de activar: > [!TIP] > > El MCP tiene un objetivo específico. > [!TIP] > > Las credenciales tienen permisos mínimos. > [!TIP] > > Las acciones de escritura requieren confirmación cuando hay riesgo. > [!TIP] > > El agente tiene instrucciones de fallback si el MCP falla. > [!TIP] > > Hay una forma clara de desconectar el MCP si algo sale mal. *** ## Cómo probar un MCP | Prueba | Qué validar | | - | - | | Consulta simple | El agente obtiene el dato correcto | | Dato inexistente | El agente no inventa | | Error de conexión | El agente explica y ofrece handoff | | Permiso insuficiente | El agente no insiste ni promete acciones | | Acción sensible | Pide confirmación o escala | Ejemplo de fallback: ```text theme={null} No pude consultar el sistema externo en este momento. Puedo tomar tus datos y pasar el caso al equipo para revisión. ``` *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Exponer demasiadas capacidades | El agente puede usar herramientas innecesarias | Habilita solo lo requerido | | Credenciales amplias | Riesgo operativo | Usa permisos mínimos | | Sin fallback | Respuestas falsas ante fallos | Define salida clara | | MCP para datos estáticos | Complejidad innecesaria | Usa Knowledge | | No conectar con un subagent | Uso fuera de contexto | Asigna MCPs a flujos concretos | | Dejar que la IA adivine el teléfono | Número incorrecto o tool fallida | Mapea `phone` o `waId` a `{{contact.phoneNumber}}` | *** ## Relación con Tools y Subagents Un MCP suele ser la conexión. Una tool o subagent decide cuándo usarla. ```mermaid theme={null} flowchart LR subagent["Subagent de soporte"] --> tool["Tool crear ticket"] tool --> mcp["MCP Helpdesk"] mcp --> external["Sistema externo"] ``` Esto permite que el agente no tenga acceso libre a todo. Accede a capacidades específicas cuando la conversación lo requiere. *** ## Siguiente paso > **[Tools](/guides/agent-tools)** > > Define las acciones que usarán esos conectores. > **[Subagents](/guides/agent-subagents)** > > Decide qué especialista puede usar cada MCP. # Configurar Subagents Source: https://docs.whaapy.com/guides/agent-subagents Cómo dividir responsabilidades del agente en especialistas de ventas, soporte, agenda y seguimiento # Configurar Subagents La tab `Subagents` permite separar responsabilidades del agente principal en especialistas. Un subagent no es otro bot aislado. Es una forma de darle al agente principal una capacidad especializada con objetivo, reglas y límites propios. *** ## Cuándo crear un subagent Crea un subagent cuando un flujo: * tiene un objetivo claro y repetible * requiere instrucciones distintas al agente principal * usa herramientas específicas * tiene criterios propios de éxito * debe escalar a humano bajo reglas particulares * hace que el prompt principal crezca demasiado No crees un subagent para una regla pequeña. Para eso usa `Control`, `Knowledge` o el prompt general. > [!TIP] > > Si puedes describir la tarea en una frase, probablemente no necesitas subagent. Si necesitas un flujo de varios pasos, sí puede valer la pena. *** ## Subagents comunes > **Ventas** > > Califica intención, recomienda productos o servicios y guía hacia compra, cotización o agenda. > **Soporte** > > Pide datos mínimos, clasifica problema, consulta información y decide si escala. > **Agenda** > > Coordina fecha, hora, servicio, confirmación y cambios de cita. > **Calificación** > > Evalúa si un lead cumple requisitos antes de pasarlo a ventas. > **Seguimiento** > > Retoma conversaciones abiertas o abandonadas con un objetivo específico. > **Cobranza** > > Explica saldos, próximos pasos y límites antes de escalar a humano. *** ## Anatomía de un buen subagent | Campo | Qué define | | - | - | | Nombre | Identidad clara: `Ventas`, `Soporte`, `Agenda`, `Seguimiento` | | Objetivo | Resultado que debe buscar | | Alcance | Qué puede y no puede resolver | | Trigger | Cuándo se activa | | Instrucciones | Cómo debe comportarse | | Tools permitidas | Acciones que puede usar | | Handoff | Cuándo devuelve o escala a humano | | Criterio de éxito | Cómo saber que terminó bien | *** ## Cómo configurarlo **1. Define el objetivo** Escribe una meta concreta: "calificar leads para una cita", "resolver dudas de envíos" o "agendar servicios". **2. Define activadores** Agrega frases, intenciones o condiciones que deberían activar el subagent. **3. Limita el alcance** Di explícitamente qué no debe resolver. Esto evita que invada otros flujos. **4. Asigna knowledge y tools** Conecta solo la información y acciones necesarias para esa tarea. **5. Define handoff** Decide cuándo debe pasar a humano o devolver el control al agente principal. **6. Prueba conversaciones** Usa ejemplos reales: casos fáciles, ambiguos, incompletos y casos que deben escalar. *** ## Ejemplo: subagent de ventas ```text theme={null} Nombre: Ventas Objetivo: Ayudar al cliente a elegir el servicio correcto y llevarlo a cotización o agenda. Activadores: - "quiero precio" - "me interesa" - "qué paquete me recomiendas" - "quiero contratar" Alcance: Puede explicar servicios, hacer preguntas de calificación y recomendar siguientes pasos. No puede prometer descuentos no documentados ni cerrar condiciones especiales. Tools: - buscar catálogo - consultar disponibilidad - transferir a humano Handoff: Transferir si el cliente pide descuento especial, contrato empresarial o negociación manual. ``` *** ## Ejemplo: subagent de soporte ```text theme={null} Nombre: Soporte Objetivo: Entender el problema del cliente y resolver dudas frecuentes antes de escalar. Activadores: - "tengo un problema" - "no funciona" - "quiero reclamar" - "necesito ayuda con mi pedido" Alcance: Puede pedir datos mínimos, consultar políticas y explicar siguientes pasos. No debe inventar estados de pedido si no tiene una tool conectada. Tools: - consultar pedido - crear ticket - transferir a humano Handoff: Transferir si hay enojo, datos sensibles, reclamo legal o error repetido. ``` *** ## Triggers de activación ### Buenos triggers ```text theme={null} quiero cotizar quiero agendar tengo un problema con mi pedido quiero hablar con soporte cuanto cuesta ``` ### Triggers riesgosos ```text theme={null} hola ayuda info producto ``` Los triggers genéricos activan subagents en conversaciones donde no hacen falta. *** ## Handoff entre subagents y humano Define qué pasa cuando el subagent no puede completar la tarea. Opciones comunes: * devolver control al agente principal * pedir un dato faltante * activar una tool de handoff * pausar la IA * transferir a un equipo humano > [!WARNING] > > Un subagent sin reglas de salida puede quedarse intentando resolver casos que deberían pasar a humano. *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Subagent demasiado amplio | Se vuelve otro agente principal | Dale un objetivo estrecho | | Triggers genéricos | Se activa cuando no debe | Usa intención concreta | | Sin handoff | Se atasca en casos complejos | Define salida clara | | Demasiadas tools | Acciones impredecibles | Permite solo lo necesario | | Duplicar knowledge | Mantenimiento difícil | Usa Knowledge compartida | *** ## Siguiente paso > **[Tools](/guides/agent-tools)** > > Asigna acciones concretas a cada especialista. > **[MCPs](/guides/agent-mcps)** > > Conecta sistemas externos cuando el subagent necesite datos o acciones fuera de Whaapy. # Configurar Tools Source: https://docs.whaapy.com/guides/agent-tools Cómo habilitar acciones y consultas que el agente puede usar durante una conversación # Configurar Tools La tab `Tools` define qué acciones puede ejecutar o consultar el agente. Una tool es una capacidad controlada. Le permite al agente hacer algo más que responder texto: consultar disponibilidad, buscar productos, disparar una automatización, transferir a humano o leer una fuente conectada. *** ## Cuándo usar Tools | Caso | Va en Knowledge | Va en Tools | | - | - | - | | Horario general del negocio | Sí | No | | Disponibilidad exacta de agenda | No | Sí | | Política de cambios | Sí | No | | Estado de un pedido | No | Sí | | Catálogo pequeño y estable | Sí | Opcional | | Inventario vivo | No | Sí | | Transferir a humano | No | Sí | | Disparar flujo externo | No | Sí | > [!NOTE] > > Si el agente puede responder leyendo información estable, usa [Knowledge](/guides/agent-knowledge). Si necesitas una regla operativa del dashboard, usa [Automatizaciones](/concepts/automations). Si el agente necesita consultar o ejecutar algo durante su turno, usa Tools. *** ## Tipos comunes de tools > **Agenda** > > Consultar disponibilidad, reservar, reprogramar o cancelar citas. > **Catálogo** > > Buscar productos, servicios, variantes, precios o disponibilidad. > **Triggers** > > Disparar flujos externos como n8n, Zapier, CRM o workflows internos. > **Handoff** > > Transferir conversación a humano o marcar que requiere atención. > **Datos** > > Consultar pedidos, tickets, suscripciones o registros del negocio. > **Re-engagement** > > Retomar conversaciones abandonadas con reglas de negocio. *** ## Cómo habilitar una tool **1. Elige la tool** Activa solo la capacidad que el agente necesita. No habilites todo por defecto. **2. Define alcance** Especifica cuándo debe usarla, qué puede hacer y cuándo debe pedir confirmación. **3. Configura permisos** Limita acciones sensibles. Consultar disponibilidad no es lo mismo que reservar una cita. **4. Escribe ejemplos** Agrega frases reales de clientes que deberían activar esa tool. **5. Prueba casos felices y fallos** Verifica qué responde si la tool no tiene datos, falla o devuelve varias opciones. **6. Activa en producción** Solo después de probar con conversaciones representativas. *** ## Cómo escribir instrucciones para una tool ### Bueno ```text theme={null} Usa esta tool cuando el cliente quiera agendar, cambiar o cancelar una cita. Antes de reservar, confirma nombre, fecha, hora y servicio. Si no hay disponibilidad, ofrece hasta 3 horarios alternativos. Si el cliente insiste en un horario no disponible, transfiere a humano. ``` ### Malo ```text theme={null} Usar para citas. ``` La segunda instrucción no define límites ni comportamiento ante errores. *** ## Permisos y riesgo | Tipo de tool | Riesgo | Recomendación | | - | - | - | | Solo lectura | Bajo | Puede activarse antes | | Buscar catálogo | Bajo a medio | Validar que no invente disponibilidad | | Crear evento | Medio | Pedir confirmación antes | | Cancelar pedido | Alto | Requiere confirmación clara o humano | | Ejecutar cobro | Alto | Mejor con flujo externo controlado | | Handoff | Bajo | Conviene tenerlo siempre disponible | > [!WARNING] > > No habilites acciones irreversibles sin confirmación. Si una tool puede cancelar, cobrar, borrar o modificar datos sensibles, limita su uso. *** ## Qué pasa si una tool falla Define comportamiento de fallback. Buenas respuestas: * "No pude consultar disponibilidad en este momento. Te transfiero con el equipo." * "No tengo acceso al estado exacto del pedido ahora. ¿Quieres que soporte lo revise?" * "Encontré varias opciones. Te muestro las principales y puedo filtrar por precio o categoría." Malas respuestas: * "Listo, quedó agendado" cuando la reserva no se confirmó. * "Sí hay disponibilidad" sin haber recibido disponibilidad. * "No existe" cuando la herramienta falló. *** ## Pruebas mínimas > [!TIP] > > El caso feliz: el cliente pide algo simple y la tool responde bien. > [!TIP] > > Faltan datos: el cliente no da fecha, servicio, pedido o identificador. > [!TIP] > > No hay resultados: agenda llena, producto no encontrado o pedido inexistente. > [!TIP] > > Error externo: la tool no responde o devuelve error. > [!TIP] > > Handoff: el agente sabe cuándo transferir a humano. *** ## Errores comunes | Error | Qué pasa | Cómo corregirlo | | - | - | - | | Habilitar demasiadas tools | El agente intenta acciones innecesarias | Empieza con pocas y específicas | | No definir cuándo usarla | Llamadas erráticas | Escribe triggers claros | | Sin fallback | Respuestas falsas ante errores | Define respuesta de fallo | | Acciones sin confirmación | Riesgo operativo | Pide confirmación antes de modificar datos | | Tool para información estática | Complejidad innecesaria | Usa Knowledge | *** ## Tools, Subagents y MCPs Una tool puede vivir dentro de un flujo más grande. ```mermaid theme={null} flowchart LR customer["Cliente"] --> salesSubagent["Subagent de ventas"] salesSubagent --> catalogTool["Tool de catalogo"] salesSubagent --> handoffTool["Tool de handoff"] catalogTool --> response["Respuesta"] handoffTool --> human["Humano"] ``` Si la acción viene de un sistema externo, probablemente la tool se apoyará en un [MCP](/guides/agent-mcps). En esa tab puedes [mapear campos de identidad](/guides/agent-mcps#campos-de-una-tool-mcp) a variables como `{{contact.phoneNumber}}` para que la IA no los pida de nuevo. *** ## Siguiente paso > **[Subagents](/guides/agent-subagents)** > > Diseña especialistas para ventas, soporte, agenda o seguimiento. > **[MCPs](/guides/agent-mcps)** > > Conecta sistemas externos con permisos y pruebas claras. > **[Automatizaciones](/concepts/automations)** > > Crea reglas para tags, funnels, mensajes, handoff y webhooks. # Acciones de automatizaciones Source: https://docs.whaapy.com/guides/automations/actions Qué puede hacer una automatización y cómo ordenar acciones de forma segura # Acciones de automatizaciones Las acciones definen **qué hace** Whaapy cuando el disparador de una automatización coincide. Las acciones se ejecutan en orden. Ese orden importa: no es lo mismo asignar un agente antes de enviar un mensaje que hacerlo después. *** ## Acciones disponibles | Acción | Para qué sirve | | - | - | | `add_tag` | Agregar una etiqueta al contacto | | `remove_tag` | Quitar una etiqueta | | `set_custom_field` | Guardar un valor en un campo personalizado | | `move_funnel_stage` | Mover el contacto a una etapa del funnel | | `send_message` | Enviar texto, botones o listas | | `send_template` | Enviar un template aprobado de WhatsApp | | `toggle_ai` | Activar, apagar o pausar la IA | | `http_request` | Llamar un endpoint externo | | `assign_agent` | Asignar la conversación a un miembro del equipo | | `update_contact` | Mapear valores hacia campos del contacto | *** ## Orden recomendado Para una automatización comercial típica: **1. Clasificar** Agrega tag o actualiza custom fields. **2. Mover** Cambia etapa del funnel si aplica. **3. Asignar** Asigna agente o equipo responsable. **4. Responder** Envía mensaje o template. **5. Coordinar IA** Pausa o apaga la IA si el flujo ya fue cubierto. **6. Notificar externo** Envía HTTP request o deja que n8n tome el flujo si necesitas orquestación compleja. No es obligatorio seguir este orden, pero ayuda a evitar estados incompletos. *** ## Mensajes `send_message` sirve para responder dentro de la ventana de 24 horas de WhatsApp. Puede ser: * texto simple * botones * lista Ejemplo: ```text theme={null} ¡Gracias por escribirnos! Ya asignamos tu solicitud al equipo correcto. ``` > [!WARNING] > > Si el cliente está fuera de la ventana de 24 horas, usa `send_template` en lugar de mensaje libre. *** ## Templates `send_template` usa templates aprobados por Meta. Úsalo para: * primer contacto * mensajes fuera de ventana de 24 horas * confirmaciones * recordatorios * seguimiento comercial Puedes llenar variables del template con valores estáticos o variables de Whaapy. Ver [Variables](/guides/automations/variables). *** ## Control de IA `toggle_ai` permite coordinar automatizaciones con el agente IA. Modos típicos: | Modo | Cuándo usarlo | | - | - | | Encender IA | Retomar automatización conversacional después de un flujo manual | | Apagar IA | Casos que deben quedar en atención humana | | Pausar IA | Evitar doble respuesta por un tiempo | Ver [Automations e IA](/guides/automations/ai-handoff). *** ## HTTP request `http_request` llama un endpoint externo. Úsalo para: * enviar lead a CRM * notificar Slack o Discord * disparar un webhook de n8n * crear ticket en helpdesk * avisar a un sistema propio Buenas prácticas: * usa HTTPS * define timeout corto * envía solo datos necesarios * prueba el webhook antes de activar * no dependas de respuestas lentas > [!TIP] > > Si el flujo externo tiene muchos pasos, usa [n8n](/integrations/n8n) y llama un webhook de n8n desde la automatización. *** ## Asignar agente `assign_agent` decide quién debe atender la conversación. Modos: | Modo | Cómo funciona | | - | - | | `fixed` | Siempre asigna al mismo agente | | `round_robin` | Rota entre agentes | | `least_loaded` | Elige quien tiene menos carga activa | | `random` | Elige uno al azar del pool | Usa fallback si el agente o pool no está disponible. *** ## Actualizar contacto `update_contact` sirve para mapear datos hacia el contacto. Es especialmente útil con Meta Lead Ads: ```text theme={null} {{lead.field.email}} -> email {{lead.field.company}} -> company {{lead.field.city}} -> city ``` Puedes usar campos nativos o custom fields. Ver [Meta Lead Ads](/guides/automations/meta-leads). *** ## Acciones seguras vs riesgosas | Acción | Riesgo | Recomendación | | - | - | - | | Agregar tag | Bajo | Buena primera acción | | Mover etapa | Medio | Asegúrate de que el trigger sea correcto | | Enviar mensaje | Medio | Evita dobles respuestas con IA | | Enviar template | Medio | Revisa variables y aprobación | | HTTP request | Medio/alto | Prueba y limita payload | | Asignar agente | Medio | Usa fallback | | Pausar/apagar IA | Medio | Documenta cuándo se reactiva | *** ## Errores comunes | Error | Qué pasa | Cómo evitarlo | | - | - | - | | Muchas acciones en una sola regla | Difícil de depurar | Divide en reglas claras | | Enviar mensaje y dejar IA activa | Doble respuesta | Usa `Omitir IA` o `toggle_ai` | | HTTP request sin prueba | Fallos silenciosos | Usa test webhook | | Template con variable vacía | Mensaje raro o inválido | Usa variables con fallback operativo | | Asignar sin fallback | Conversación sin dueño | Define agente alterno | *** ## Siguiente paso Aprende a insertar datos dinámicos en [Variables](/guides/automations/variables). # Automations e IA Source: https://docs.whaapy.com/guides/automations/ai-handoff Cómo coordinar automatizaciones con el agente IA, pausas y handoff humano # Automations e IA Las automatizaciones pueden trabajar junto al agente IA. La parte importante es evitar que dos sistemas respondan al mismo cliente al mismo tiempo. *** ## Problema común: doble respuesta Un cliente escribe: ```text theme={null} quiero hablar con un asesor ``` Podrían responder tres cosas: * una automatización * el agente IA * una integración externa Si no coordinas el flujo, el cliente recibe varias respuestas y la experiencia se rompe. *** ## Herramientas para coordinar | Configuración | Para qué sirve | | - | - | | `Omitir IA` | La automatización se encarga y la IA no responde a ese evento | | `toggle_ai` | Activa, apaga o pausa la IA como acción | | `Parar al coincidir` | Evita que otras automatizaciones también ejecuten | | Triggers post-IA | Reaccionan a cosas que pasan después de la IA | | Control por conversación | Pausa o apaga IA para un hilo específico | Ver [Control de IA](/guides/agent-control) y [Control por Conversación](/api-reference/conversations/ai-control). *** ## Cuándo usar Omitir IA Activa `Omitir IA` cuando la automatización ya cubre la respuesta del cliente. Ejemplos: * fuera de horario * cliente pide humano * lead de Meta recibe template * trigger manda mensaje de bienvenida * webhook externo se encargará del siguiente paso > [!WARNING] > > Si una automatización envía mensaje y no omites IA, el agente puede responder también. *** ## Cuándo usar toggle\_ai Usa la acción `toggle_ai` cuando quieres cambiar el estado de la IA. | Modo | Ejemplo | | - | - | | Pausar | Humano atenderá durante 30 minutos | | Apagar | Cliente VIP requiere atención manual | | Encender | El flujo manual terminó y la IA puede volver | Ejemplo: ```text theme={null} Trigger: contiene "humano" Acciones: 1. Agregar tag "requiere-humano" 2. Pausar IA 60 minutos 3. Asignar agente 4. Enviar mensaje corto ``` *** ## Triggers relacionados con IA ### User replied to AI Úsalo cuando quieres reaccionar después de que el usuario responde a la IA. Casos: * cliente muestra interés después de una respuesta de IA * cliente pide aclaración * cliente pide humano * quieres mover etapa por respuesta a la IA ### AI scheduled appointment Úsalo cuando el agente agendó una cita. Casos: * mover a etapa `Cita agendada` * asignar agente * enviar template de confirmación * avisar a n8n o CRM *** ## Handoff humano Un buen handoff hace tres cosas: **1. Detecta intención** Trigger por keyword, respuesta a IA o regla de soporte. **2. Pausa o apaga IA** Usa `Omitir IA` y/o `toggle_ai`. **3. Asigna responsable** Usa `assign_agent` con fallback. **4. Confirma al cliente** Envía un mensaje corto diciendo que una persona lo atenderá. Mensaje ejemplo: ```text theme={null} Claro, te paso con una persona del equipo para que te ayude mejor. ``` *** ## Patrón recomendado: humano toma control ```mermaid theme={null} flowchart LR message["Cliente pide humano"] --> trigger["Trigger keywords"] trigger --> skipAi["Omitir IA"] skipAi --> pause["Pausar IA"] pause --> assign["Asignar agente"] assign --> reply["Mensaje de confirmación"] ``` *** ## Patrón recomendado: IA agenda cita ```mermaid theme={null} flowchart LR aiEvent["IA agendó cita"] --> stage["Mover funnel"] stage --> assign["Asignar agente"] assign --> template["Enviar template"] template --> notify["Notificar CRM o n8n"] ``` *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Automation e IA responden juntas | Cliente recibe doble respuesta | Usa `Omitir IA` | | Apagar IA globalmente por casos puntuales | Afectas todo el negocio | Usa control por conversación | | No asignar agente en handoff | Nadie toma el caso | Usa `assign_agent` | | Pausa demasiado corta | La IA vuelve antes de tiempo | Ajusta duración según proceso | | No confirmar handoff | Cliente no sabe qué pasó | Envía mensaje corto | *** ## Enlaces útiles > **[colme-2](/concepts/colme-2)** > > Entiende cómo el agente convive con control, tools y handoff. > **[Control del Agente](/guides/agent-control)** > > Configura pausas, handoff y comportamiento de IA. > **[Control por Conversación](/api-reference/conversations/ai-control)** > > Controla IA por conversación desde API. > **[Actions](/guides/automations/actions)** > > Diseña acciones seguras para evitar duplicados. # Crear tu primera automatización Source: https://docs.whaapy.com/guides/automations/getting-started Cómo crear, probar y activar una automatización en Whaapy # Crear tu primera automatización > [!TIP] > > Míralo en video: **Automatizaciones completo** (23 min). [Todos los videos de Automatizaciones](/tutorials/automatizaciones). Esta guía te lleva por el flujo básico para crear una automatización desde el dashboard de Whaapy. El objetivo es crear una regla simple, probarla y activarla sin mezclar demasiadas condiciones desde el inicio. *** ## Antes de empezar Necesitas: > [!TIP] > > Acceso admin al dashboard de Whaapy. > [!TIP] > > Un número de WhatsApp conectado. > [!TIP] > > Una idea clara del evento que debe disparar la automatización. Para esta guía usaremos un caso simple: ```text theme={null} Cuando un cliente escriba "quiero cotizar", agregar tag "cotizacion" y enviar un mensaje de seguimiento. ``` *** ## Paso 1: Crear la regla Ve a `Automations` en el dashboard y selecciona **Nueva**. Completa: | Campo | Ejemplo | | - | - | | Nombre | `Lead pide cotización` | | Descripción | `Detecta intención de cotizar y prepara seguimiento` | | Estado | Inactiva mientras la configuras | > [!TIP] > > Usa nombres que expliquen el resultado, no solo el trigger. `Lead pide cotización` es mejor que `keyword cotizar`. *** ## Paso 2: Elegir trigger Para este ejemplo usa `contains`. ```text theme={null} Texto contiene: quiero cotizar Case sensitive: desactivado ``` Esto hará que la regla coincida con mensajes como: ```text theme={null} Hola, quiero cotizar un servicio Quiero cotizar para mi empresa ``` Si necesitas varias frases, usa `keywords`. Si necesitas una condición más estricta, usa `exact_match` o `regex`. Ver [Triggers](/guides/automations/triggers). *** ## Paso 3: Agregar acciones Agrega acciones en el orden en que deben ejecutarse. Ejemplo: **1. Agregar tag** Usa `add_tag` con el valor `cotizacion`. **2. Mover etapa** Usa `move_funnel_stage` para mover el contacto a una etapa como `Cotización`. **3. Enviar mensaje** Usa `send_message` para responder con un texto corto. Mensaje ejemplo: ```text theme={null} ¡Claro! Te puedo ayudar con una cotización. ¿Me compartes qué servicio necesitas y para cuándo? ``` *** ## Paso 4: Configurar opciones avanzadas Para un primer flujo: | Opción | Recomendación | | - | - | | Prioridad | Normal | | Ejecutar una vez | Activar si no quieres repetir la misma bienvenida | | Omitir IA | Activar si el mensaje automático ya responde el caso | | Parar al coincidir | Activar si no quieres que otras reglas también respondan | > [!WARNING] > > Si usas `send_message` y además dejas que la IA responda al mismo mensaje, puedes generar doble respuesta. Usa `Omitir IA` cuando la automatización ya cubre el caso. *** ## Paso 5: Probar trigger Usa la opción de prueba del builder con frases reales. Prueba mensajes que sí deberían coincidir: ```text theme={null} quiero cotizar hola quiero cotizar un servicio me puedes cotizar? ``` Y mensajes que no deberían coincidir: ```text theme={null} hola quiero soporte tengo una queja ``` Si coinciden demasiados mensajes, usa un trigger más específico o agrega keywords más precisas. *** ## Paso 6: Activar y revisar Cuando la prueba sea correcta: 1. Guarda la automatización. 2. Actívala desde la lista. 3. Prueba con una conversación real controlada. 4. Revisa que solo se ejecuten las acciones esperadas. Desde la lista puedes: * activar o desactivar * duplicar para crear variantes * editar * eliminar * buscar por nombre o descripción *** ## Buenas prácticas * Empieza con una automatización pequeña. * Usa una acción segura antes de acciones externas. * Prueba con frases reales de clientes. * Activa `Omitir IA` cuando la automatización responde por completo. * Usa `Parar al coincidir` en reglas exclusivas. * Duplica reglas existentes para crear variaciones sin empezar desde cero. *** ## Siguiente paso > **[Triggers](/guides/automations/triggers)** > > Aprende qué dispara una automatización y cuándo usar cada tipo. > **[Actions](/guides/automations/actions)** > > Diseña acciones seguras y ordenadas. # Automatizaciones con Meta Lead Ads Source: https://docs.whaapy.com/guides/automations/meta-leads Cómo usar automatizaciones para procesar leads recibidos desde formularios de Meta # Automatizaciones con Meta Lead Ads Las automatizaciones con Meta Lead Ads te permiten actuar cuando llega un lead desde Facebook o Instagram. Puedes crear o actualizar el contacto, mapear campos del formulario, asignar agente, mover el lead a una etapa, enviar un template y notificar a sistemas externos. *** ## Antes de empezar Necesitas: > [!TIP] > > La integración de Meta Lead Ads conectada. > [!TIP] > > Páginas y formularios sincronizados. > [!TIP] > > Templates aprobados si quieres contactar al lead fuera de la ventana permitida. > [!TIP] > > Campos del formulario claros: nombre, teléfono, email y preguntas relevantes. Si todavía no ves formularios, revisa la integración en el dashboard: `Integraciones -> Meta Lead Ads`. *** ## Trigger Usa el trigger `meta_lead_received`. Puedes configurarlo de tres formas: | Configuración | Resultado | | - | - | | Sin página ni formulario | Cualquier lead sincronizado | | Página específica | Leads de esa página | | Uno o varios formularios | Solo leads de esos formularios | > [!TIP] > > Si tienes campañas con objetivos distintos, crea una automatización por formulario. Es más fácil medir y mantener. *** ## Flujo recomendado ```mermaid theme={null} flowchart LR lead["Lead recibido"] --> updateContact["Actualizar contacto"] updateContact --> tag["Agregar tag"] tag --> stage["Mover etapa"] stage --> assign["Asignar agente"] assign --> template["Enviar template"] template --> notify["Notificar CRM o n8n"] ``` *** ## Paso 1: Actualizar contacto Usa `update_contact` para mapear respuestas del formulario hacia campos del contacto. Ejemplo: ```text theme={null} {{lead.field.full_name}} -> name {{lead.field.email}} -> email {{lead.field.company}} -> company {{lead.field.city}} -> city {{lead.field.budget}} -> custom:presupuesto ``` El builder puede sugerir mappings cuando seleccionas un formulario específico. > [!NOTE] > > El teléfono normalmente se usa para crear o encontrar el contacto. No necesitas mapearlo manualmente salvo que tengas un caso especial. *** ## Paso 2: Clasificar el lead Acciones útiles: * `add_tag`: `meta-lead`, `campaña-abril`, `lead-frio`, `lead-caliente` * `set_custom_field`: guardar campaña, presupuesto o servicio de interés * `move_funnel_stage`: mover a `Nuevo lead`, `Contactar`, `Cotización` Mantén una convención simple para tags. Evita crear tags distintos para lo mismo. *** ## Paso 3: Asignar agente Usa `assign_agent` si alguien debe atender el lead. Modos recomendados: | Modo | Cuándo usarlo | | - | - | | `fixed` | Un solo responsable para esa campaña | | `round_robin` | Repartir leads entre vendedores | | `least_loaded` | Balancear carga activa | | `random` | Reparto simple sin prioridad | Configura fallback para evitar que un lead quede sin dueño. *** ## Paso 4: Enviar template Si vas a escribir al lead por WhatsApp y no hay ventana activa, usa `send_template`. Ejemplo: ```text theme={null} Hola {{contact.first_name}}, vimos que solicitaste información. Soy {{agent.first_name}} de Whaapy, ¿te puedo ayudar por aquí? ``` Si el template tiene variables, usa [Variables](/guides/automations/variables) para llenar nombre, agente, servicio o campaña. *** ## Paso 5: Notificar a sistemas externos Si tu CRM o equipo necesita enterarse: * usa `http_request` para un webhook directo * usa [n8n](/integrations/n8n) si el flujo tiene varios pasos * manda solo los datos necesarios Payload ejemplo: ```json theme={null} { "source": "meta_lead_ads", "name": "{{contact.name}}", "phone": "{{contact.phoneNumber}}", "email": "{{lead.field.email}}", "agent": "{{agent.email}}" } ``` *** ## Opciones avanzadas | Opción | Recomendación | | - | - | | Ejecutar una vez | Activar si el mismo lead no debe procesarse varias veces | | Omitir IA | Activar si envías template o asignas humano inmediatamente | | Parar al coincidir | Activar si este flujo no debe combinarse con otras reglas | | Prioridad | Alta si el lead debe procesarse antes de otras automatizaciones | *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Usar cualquier formulario para todo | Leads mal clasificados | Crea reglas por formulario | | No mapear email o empresa | Contactos incompletos | Usa `update_contact` | | Sin fallback de agente | Leads sin responsable | Define fallback | | Enviar texto libre fuera de ventana | Mensaje falla | Usa template aprobado | | No probar payload externo | CRM recibe datos incompletos | Usa test webhook | *** ## Siguiente paso Si el lead debe pasar a CRM, revisa [Webhooks y n8n](/guides/automations/webhooks-and-n8n). # Disparadores de automatizaciones Source: https://docs.whaapy.com/guides/automations/triggers Qué dispara una automatización y cómo elegir el trigger correcto # Disparadores de automatizaciones Un disparador define **cuándo** debe ejecutarse una automatización. Elegir bien el disparador evita falsos positivos, respuestas duplicadas y reglas que se disparan cuando no deberían. *** ## Tipos disponibles | Trigger | Cuándo usarlo | | - | - | | `exact_match` | El mensaje debe ser exactamente igual | | `contains` | El mensaje contiene una frase | | `keywords` | Cualquier palabra o frase de una lista | | `regex` | Patrón avanzado | | `first_message` | Primer mensaje de un contacto o conversación nueva | | `user_replied_to_ai` | El usuario respondió después de una respuesta de IA | | `ai_scheduled_appointment` | La IA agendó una cita | | `pipeline_stage_entered` | Un contacto entró a una etapa del funnel | | `time_range` | Mensajes dentro de horario o días específicos | | `meta_lead_received` | Se recibió un lead desde Meta Lead Ads | *** ## Triggers de texto ### Exact match Usa `exact_match` cuando el mensaje debe coincidir exactamente. Bueno para respuestas de botones o palabras controladas: ```text theme={null} confirmar cancelar hablar con asesor ``` No es ideal para lenguaje natural. Si el cliente escribe "quiero confirmar", ya no coincide. ### Contains Usa `contains` cuando quieres detectar una frase dentro del mensaje. ```text theme={null} quiero cotizar hablar con humano necesito soporte ``` Es más flexible que `exact_match`, pero puede dispararse de más si la frase es muy genérica. ### Keywords Usa `keywords` cuando tienes varias formas de expresar la misma intención. ```text theme={null} cotizar precio costos cuánto cuesta paquete ``` Ideal para intención comercial o soporte básico. ### Regex Usa `regex` solo cuando necesitas un patrón real. Ejemplos: ```text theme={null} ORD-[0-9]{6} ^cancelar\\s+[a-z0-9_-]+$ ``` > [!WARNING] > > Regex mal diseñado puede coincidir demasiado o nada. Si no necesitas patrón, usa `contains` o `keywords`. *** ## Triggers de conversación ### First message Se ejecuta cuando el contacto inicia por primera vez una conversación. Casos útiles: * bienvenida * tag de nuevo contacto * mover a etapa "Nuevo lead" * pedir datos iniciales Evita mandar mensajes largos de venta aquí. Una bienvenida corta suele funcionar mejor. ### User replied to AI Se ejecuta cuando el usuario responde después de que la IA participó. Casos útiles: * detectar que el cliente volvió después de una respuesta automática * pausar IA si el usuario pide humano * enviar evento a CRM cuando la IA generó interés ### AI scheduled appointment Se ejecuta cuando el agente IA agenda una cita. Casos útiles: * mover contacto a etapa "Cita agendada" * enviar template de confirmación * asignar agente responsable * notificar a n8n o CRM *** ## Triggers de operación ### Pipeline stage entered Se ejecuta cuando un contacto entra a una etapa específica del funnel. Ejemplos: ```text theme={null} Nuevo lead -> enviar bienvenida Cotización -> asignar vendedor Cita agendada -> enviar recordatorio Ganado -> agregar tag cliente ``` Úsalo para automatizar operación comercial, no para interpretar mensajes. ### Time range Se ejecuta cuando el evento ocurre dentro de un horario o días definidos. Casos útiles: * fuera de horario * fin de semana * horario de soporte * campañas por ventana horaria Ejemplo: ```text theme={null} Si llega un mensaje de 19:00 a 08:00, enviar respuesta fuera de horario y omitir IA. ``` *** ## Trigger de Meta Lead Ads `meta_lead_received` se ejecuta cuando Whaapy recibe un lead desde formularios conectados de Meta. Puedes filtrar por: * página * formulario específico * varios formularios * cualquier formulario sincronizado Ver [Meta Lead Ads](/guides/automations/meta-leads). *** ## Cómo elegir | Necesidad | Trigger recomendado | | - | - | | Cliente escribe una frase exacta | `exact_match` | | Cliente menciona una intención | `contains` o `keywords` | | Detectar código/patrón | `regex` | | Primer contacto | `first_message` | | Reacción después de IA | `user_replied_to_ai` | | Cita creada por IA | `ai_scheduled_appointment` | | Cambio de etapa comercial | `pipeline_stage_entered` | | Fuera de horario | `time_range` | | Lead de Facebook/Instagram | `meta_lead_received` | *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Keywords demasiado genéricas | Se disparan reglas sin intención clara | Usa frases más específicas | | Regex innecesario | Difícil de mantener | Usa `contains` o `keywords` | | Varias reglas para el mismo mensaje | Acciones duplicadas | Usa prioridad y `Parar al coincidir` | | Trigger de texto para operación interna | Reglas frágiles | Usa funnel, IA o Meta Lead triggers | | No probar negativos | Falsos positivos | Prueba mensajes que no deberían coincidir | *** ## Siguiente paso Después de elegir trigger, diseña acciones seguras en [Actions](/guides/automations/actions). # Variables en automatizaciones Source: https://docs.whaapy.com/guides/automations/variables Cómo usar variables dinámicas en mensajes, templates, webhooks y actualización de contactos # Variables en automatizaciones Las variables permiten insertar datos dinámicos dentro de acciones. Puedes usarlas en mensajes, templates, HTTP requests, acciones de actualización de contacto, Tools HTTP y [campos de tools MCP](/guides/agent-mcps#campos-de-una-tool-mcp). *** ## Formato Las variables usan doble llave: ```text theme={null} {{contact.name}} {{system.dia_actual}} {{lead.field.email}} ``` Si estás en el builder, usa **Insertar variable** para evitar errores de escritura. *** ## Variables de contacto | Variable | Qué representa | | - | - | | `{{contact.name}}` | Nombre completo | | `{{contact.first_name}}` | Primer nombre | | `{{contact.phoneNumber}}` | Teléfono | | `{{contact.email}}` | Email | Ejemplo: ```text theme={null} Hola {{contact.first_name}}, gracias por escribirnos. ``` *** ## Variables de negocio Las variables de negocio dependen de lo que tengas definido en ajustes. Formato: ```text theme={null} {{business.mi_dato}} ``` Ejemplos: ```text theme={null} {{business.nombre_sucursal}} {{business.link_catalogo}} {{business.horario_soporte}} ``` Úsalas para datos que quieres mantener centralizados. *** ## Variables del sistema | Variable | Uso común | | - | - | | `{{system.dia_actual}}` | Día actual | | `{{system.hora_actual}}` | Hora actual | | `{{system.saludo}}` | Saludo contextual | | `{{system.fecha_corta}}` | Fecha corta | | `{{system.timestamp}}` | Marca temporal | Ejemplo: ```text theme={null} {{system.saludo}}, recibimos tu solicitud el {{system.fecha_corta}}. ``` *** ## Variables de Meta Lead Ads Cuando el trigger es `meta_lead_received`, puedes usar campos del formulario: ```text theme={null} {{lead.field.full_name}} {{lead.field.email}} {{lead.field.company}} {{lead.field.city}} ``` El nombre exacto depende de las preguntas del formulario. > [!TIP] > > En automatizaciones de Meta Lead Ads, selecciona el formulario para que el builder pueda sugerir variables disponibles. *** ## Variables de agente asignado Puedes usar datos del agente asignado: | Variable | Qué representa | | - | - | | `{{agent.name}}` | Nombre del agente | | `{{agent.first_name}}` | Primer nombre | | `{{agent.email}}` | Email | | `{{agent.phone}}` | Teléfono | | `{{agent.whatsapp_phone}}` | WhatsApp | | `{{agent.title}}` | Cargo | Ejemplo: ```text theme={null} Te atenderá {{agent.first_name}} del equipo comercial. ``` Si no hay agente asignado, esas variables pueden quedar vacías. *** ## Dónde usarlas | Acción | Uso | | - | - | | `send_message` | Personalizar texto | | `send_template` | Llenar variables de template | | `http_request` | Enviar datos a otro sistema | | `update_contact` | Mapear valores a campos del contacto | | `set_custom_field` | Guardar información calculada o recibida | *** ## Ejemplos ### Mensaje personalizado ```text theme={null} Hola {{contact.first_name}}, recibimos tu solicitud. Te contactaremos hoy. ``` ### Payload HTTP ```json theme={null} { "name": "{{contact.name}}", "phone": "{{contact.phoneNumber}}", "source": "whaapy", "leadEmail": "{{lead.field.email}}" } ``` ### Actualizar contacto ```text theme={null} {{lead.field.company}} -> company {{lead.field.city}} -> city {{lead.field.budget}} -> custom:presupuesto ``` *** ## Buenas prácticas * Usa el botón **Insertar variable** siempre que esté disponible. * No asumas que todas las variables existen. * Evita mensajes que se rompan si una variable viene vacía. * Usa `update_contact` para guardar datos antes de depender de ellos en otros flujos. * En HTTP requests, envía solo los campos que realmente necesita el sistema externo. *** ## Errores comunes | Error | Resultado | Mejor enfoque | | - | - | - | | Escribir variable a mano con typo | Valor vacío o inválido | Usa Insertar variable | | Depender de `agent.*` sin asignar agente | Mensaje incompleto | Asigna agente antes o evita esa variable | | Usar campo de lead que no existe | Payload incompleto | Selecciona formulario y revisa campos | | Meter datos sensibles en HTTP request | Riesgo operativo | Envía solo lo necesario | | Usar variables en templates sin validar | Template falla o se ve raro | Prueba antes de activar | *** ## Siguiente paso Si trabajas con leads de Meta, sigue con [Meta Lead Ads](/guides/automations/meta-leads). # Automations, webhooks y n8n Source: https://docs.whaapy.com/guides/automations/webhooks-and-n8n Cuándo usar automatizaciones internas, webhooks de Whaapy, HTTP requests o n8n # Automations, webhooks y n8n Whaapy tiene varias formas de automatizar conversaciones. La clave es elegir la herramienta correcta según dónde debe vivir el flujo: dentro de Whaapy, en tu sistema o en una plataforma externa como n8n. *** ## Diferencia rápida | Herramienta | Úsala cuando | | - | - | | Automations | Quieres reglas directas dentro de Whaapy | | Webhooks de Whaapy | Tu sistema necesita recibir eventos | | HTTP request action | Una automatización debe llamar un endpoint externo | | n8n | Necesitas orquestar varios sistemas o pasos | | API directa | Tu app controla el flujo desde código | *** ## Automations internas Úsalas para flujos cercanos a la conversación: * etiquetar contacto * mover funnel * enviar mensaje o template * asignar agente * pausar IA * procesar leads de Meta * llamar un webhook externo simple Ventaja: son visibles y editables desde el dashboard. *** ## Webhooks de Whaapy Los webhooks de Whaapy envían eventos a tu sistema. Ejemplos: * `message.received` * `message.sent` * `message.delivered` * `conversation.created` * `conversation.handoff` Úsalos cuando tu sistema necesita reaccionar a eventos, guardar logs, actualizar un CRM o iniciar un proceso externo. Ver [Webhooks](/api-reference/webhooks/overview). *** ## HTTP request desde Automations La acción `http_request` permite que una automatización llame un endpoint externo. Ejemplo: ```mermaid theme={null} flowchart LR trigger["Trigger coincide"] --> actions["Acciones Whaapy"] actions --> httpRequest["HTTP request"] httpRequest --> crm["CRM o sistema externo"] ``` Úsala cuando: * el flujo externo es simple * solo necesitas enviar datos * tienes un endpoint propio * quieres probarlo desde el builder Buenas prácticas: * usa HTTPS * limita datos enviados * define timeout corto * maneja duplicados del lado receptor * responde rápido *** ## n8n Usa n8n cuando el flujo tiene varios pasos o varias apps. Ejemplos: ```text theme={null} Whaapy -> n8n -> CRM -> Slack -> Google Sheets ``` ```text theme={null} Meta Lead -> Whaapy Automation -> Webhook n8n -> CRM -> Email interno ``` La integración de n8n permite usar operaciones de Whaapy y escuchar eventos desde el nodo trigger. Ver [n8n Integration](/integrations/n8n). *** ## Cuándo usar cada opción | Caso | Recomendación | | - | - | | Etiquetar contacto cuando escribe "cotizar" | Automation | | Mandar un lead a tu CRM | Automation + HTTP request | | Sincronizar CRM, Slack y Sheets | n8n | | Escuchar todos los mensajes entrantes en tu backend | Webhooks | | Construir lógica compleja propia | API directa + webhooks | | Pausar IA al asignar humano | Automation | *** ## Evitar duplicados Ten cuidado cuando combines varias capas. Ejemplo riesgoso: ```text theme={null} Automation envía mensaje n8n también responde al webhook IA también responde al cliente ``` Resultado: el cliente recibe varias respuestas. Mejor: * la automatización hace clasificación y pausa IA * n8n procesa el flujo externo * el equipo humano o un template responde una sola vez *** ## Patrón recomendado con n8n **1. Detecta dentro de Whaapy** Usa un trigger de automation para detectar el caso. **2. Prepara contexto** Agrega tags, mueve etapa o actualiza contacto. **3. Pausa IA si aplica** Evita que el agente responda mientras el flujo externo trabaja. **4. Llama n8n** Usa `http_request` hacia un webhook de n8n. **5. n8n orquesta** CRM, Slack, email, Sheets, ERP u otras apps. *** ## Siguiente paso Si tu flujo toca la IA, revisa [Automations e IA](/guides/automations/ai-handoff). # Crear un MCP compatible con Whaapy Source: https://docs.whaapy.com/guides/mcp-server-spec Especificación técnica para exponer herramientas MCP que el agente de Whaapy pueda descubrir, validar y ejecutar correctamente # Crear un MCP compatible con Whaapy Esta guía es para desarrolladores que quieren exponer un MCP para que Whaapy pueda conectarlo a un agente, tool o subagent. Whaapy usa MCP como una capa estructurada para consultar datos o ejecutar acciones externas. Para que funcione bien, el servidor debe declarar schemas claros, recibir argumentos tipados y devolver resultados verificables. > [!NOTE] > > Si solo quieres conectar un MCP desde el dashboard, revisa [Configurar MCPs](/guides/agent-mcps). Esta página explica cómo construir el servidor MCP que Whaapy va a consumir. *** ## Resumen rápido Whaapy espera un servidor MCP por HTTP con estas capacidades: | Área | Requisito | | - | - | | Transporte | Streamable HTTP | | Protocolo | JSON-RPC 2.0 | | Versión MCP | `2025-06-18` | | Discovery | `tools/list` | | Ejecución | `tools/call` | | Schema | `inputSchema` con raíz `type: "object"` | | Respuesta | `content: [{ type: "text", text: "" }]` | Flujo completo: ```mermaid theme={null} flowchart LR whaapy["Whaapy"] -->|"initialize"| mcpServer["MCP Server"] whaapy -->|"tools/list"| toolCatalog["Tool Catalog"] whaapy -->|"tools/call"| toolHandler["Tool Handler"] toolHandler --> externalSystem["External System"] toolHandler -->|"content text JSON"| whaapy ``` *** ## Transporte Expón un endpoint HTTP para MCP: ```text theme={null} POST https://tu-dominio.com/mcp DELETE https://tu-dominio.com/mcp ``` Whaapy envía estos headers: ```http theme={null} Content-Type: application/json Accept: application/json, text/event-stream MCP-Protocol-Version: 2025-06-18 Mcp-Session-Id: ``` Métodos de autenticación soportados: | Tipo | Cómo se envía | | - | - | | `none` | Sin credenciales | | `bearer` | `Authorization: Bearer ` | | `api_key` | Header configurado, por ejemplo `X-API-Key: ` | | `custom_headers` | Uno o varios headers configurados | Timeouts actuales del cliente Whaapy: | Operación | Timeout | | - | - | | `initialize` | 15 segundos | | `tools/list` | 15 segundos | | `tools/call` | 30 segundos | | `notifications/initialized` | 5 segundos | > [!WARNING] > > Diseña tools que respondan rápido. Si una operación tarda más de 30 segundos, conviértela en una operación encolada y devuelve un estado verificable. *** ## Handshake Whaapy inicia la sesión con `initialize`. Request: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": { "name": "whaapy-mcp-client", "version": "1.0.0" } } } ``` Response: ```json theme={null} { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": {} }, "serverInfo": { "name": "mi-mcp", "version": "1.0.0" } } } ``` Después Whaapy envía la notificación `notifications/initialized`: ```json theme={null} { "jsonrpc": "2.0", "method": "notifications/initialized" } ``` El servidor puede responder `202 Accepted`. *** ## Discovery con tools/list Whaapy descubre las herramientas con `tools/list`. Request: ```json theme={null} { "jsonrpc": "2.0", "id": 123, "method": "tools/list" } ``` Response: ```json theme={null} { "jsonrpc": "2.0", "id": 123, "result": { "tools": [ { "name": "search_customer", "description": "Busca clientes por teléfono, nombre, usuario o ID externo.", "inputSchema": { "type": "object", "additionalProperties": false, "required": ["query"], "properties": { "query": { "type": "string", "description": "Texto para buscar al cliente" }, "maxResults": { "type": "integer", "description": "Máximo de resultados a devolver" } } } } ] } } ``` Whaapy guarda `name`, `description` e `inputSchema`. Después usa ese schema para validar los argumentos antes de llamar la tool. *** ## Reglas de inputSchema La raíz del `inputSchema` debe ser un objeto: ```json theme={null} { "type": "object", "additionalProperties": false, "required": ["customerId"], "properties": { "customerId": { "type": "string" } } } ``` Tipos soportados: | Tipo | Uso | | - | - | | `string` | Texto, IDs, fechas serializadas, teléfonos | | `number` | Montos, porcentajes, scores | | `integer` | Conteos, límites, cantidades enteras | | `boolean` | Banderas explícitas | | `array` | Listas de candidatos, items, errores | | `object` | Datos estructurados anidados | | `enum` | Valores permitidos | Para arrays, define `items`: ```json theme={null} { "type": "array", "items": { "type": "object", "properties": { "value": { "type": "string" }, "source": { "type": "string" } }, "required": ["value"] } } ``` Para objetos anidados, define `properties`: ```json theme={null} { "type": "object", "properties": { "identityConfirmed": { "type": "boolean" }, "customerId": { "type": "string" } } } ``` > [!WARNING] > > No declares `object` o `array` si esperas recibir un string con JSON adentro. Whaapy valida tipos reales contra el schema. *** ## Objetos y arrays reales Este es el error más común al integrar MCPs con Whaapy: pasar estructuras como strings JSON. Bueno: ```json theme={null} { "rawExtracted": { "monto": "$350.00", "banco_destino": "BBVA" }, "trackingKeyCandidates": [ { "value": "260604017724349474I", "source": "clave_rastreo", "confidence": "high" } ] } ``` Malo: ```json theme={null} { "rawExtracted": "{\"monto\":\"$350.00\",\"banco_destino\":\"BBVA\"}", "trackingKeyCandidates": "[{\"value\":\"260604017724349474I\"}]" } ``` Si el schema dice: ```json theme={null} { "rawExtracted": { "type": "object" }, "trackingKeyCandidates": { "type": "array" } } ``` entonces Whaapy espera: * `rawExtracted` como objeto real * `trackingKeyCandidates` como array real No como texto serializado. *** ## Ejecución con tools/call Cuando el agente decide usar una capacidad, Whaapy llama `tools/call`. Request: ```json theme={null} { "jsonrpc": "2.0", "id": 456, "method": "tools/call", "params": { "name": "submit_payment_evidence", "arguments": { "amount": 350, "rawExtracted": { "monto": "$350.00", "banco_destino": "BBVA" }, "trackingKeyCandidates": [ { "value": "260604017724349474I", "source": "clave_rastreo", "confidence": "high" } ] } } } ``` Whaapy valida antes de ejecutar: * campos requeridos * tipos primitivos * `enum` * arrays * objetos anidados * propiedades extra si `additionalProperties: false` Si los argumentos no pasan validación, Whaapy no llama al MCP y registra un error de validación. *** ## Respuesta de tools MCP exige que la respuesta tenga `content`. Whaapy funciona mejor si devuelves un solo bloque `text` con JSON válido. Respuesta recomendada: ```json theme={null} { "content": [ { "type": "text", "text": "{\"ok\":true,\"status\":\"success\",\"data\":{\"paymentId\":\"pay_123\",\"matched\":true},\"toolName\":\"submit_payment_evidence\",\"safety\":\"external_send\",\"durationMs\":842}" } ], "isError": false } ``` El JSON dentro de `text` debería seguir este envelope: ```json theme={null} { "ok": true, "status": "success", "data": { "paymentId": "pay_123" }, "toolName": "submit_payment_evidence", "safety": "external_send", "durationMs": 842 } ``` Campos recomendados: | Campo | Tipo | Descripción | | - | - | - | | `ok` | boolean | Si la operación tuvo éxito | | `status` | string | Estado de negocio | | `data` | object | Resultado estructurado | | `errors` | array | Errores estructurados si falló | | `toolName` | string | Nombre de la tool ejecutada | | `safety` | string | Riesgo o clase de operación | | `durationMs` | number | Duración de la operación | Estados recomendados: | Status | Cuándo usarlo | | - | - | | `success` | La operación terminó correctamente | | `failed` | No se pudo completar | | `needs_more_info` | Faltan datos del usuario | | `pending_confirmation` | Hay candidatos o acciones que requieren confirmar | | `queued` | La operación quedó en proceso | *** ## Errores Para errores técnicos del protocolo, usa error JSON-RPC: ```json theme={null} { "jsonrpc": "2.0", "id": 456, "error": { "code": -32602, "message": "Invalid arguments", "data": { "field": "customerId" } } } ``` Para errores de negocio dentro de una tool, devuelve `isError: true` y un payload estructurado: ```json theme={null} { "content": [ { "type": "text", "text": "{\"ok\":false,\"status\":\"needs_more_info\",\"errors\":[{\"code\":\"missing_customer_identity\",\"message_es\":\"Falta confirmar la identidad del cliente\",\"field\":\"customerId\",\"retryable\":true}],\"nextQuestion\":\"¿Me compartes tu número de servicio o usuario?\",\"toolName\":\"get_customer_balance\"}" } ], "isError": true } ``` Formato recomendado de errores: ```json theme={null} { "errors": [ { "code": "missing_customer_identity", "message": "Customer identity is required", "message_es": "Falta confirmar la identidad del cliente", "field": "customerId", "retryable": true } ] } ``` > [!TIP] > > Usa `message_es` cuando el agente atienda usuarios en español. Whaapy puede usar ese mensaje para explicar el siguiente paso con menos ambigüedad. *** ## Contrato semántico de una tool Además del schema, la descripción debe explicar cuándo usar la tool y qué significa un resultado correcto. Incluye estas secciones en `description`: ```text theme={null} When to use: Cuando el cliente envía un comprobante de transferencia y hay datos suficientes para validarlo. Never use for: Pagos en efectivo, comprobantes ilegibles o casos donde el cliente no corresponde. Identity: Requiere identityConfirmed=true o un candidato explícito pendiente de confirmación. Side effects: Valida el SPEI y registra evidencia de pago. Success criteria: ok=true, status=success y data.paymentId presente. Fallback: Si faltan datos, devolver status=needs_more_info con nextQuestion. ``` Buenas descripciones reducen llamadas incorrectas y evitan que el agente prometa acciones no ejecutadas. *** ## Acciones con side effects Si la tool modifica algo externo, no basta con devolver texto. Devuelve evidencia verificable: | Acción | Evidencia recomendada | | - | - | | Crear ticket | `ticketId`, `ticketUrl`, `status` | | Registrar pago | `paymentId`, `receiptId`, `matchedInvoiceIds` | | Crear cita | `eventId`, `startAt`, `calendarId` | | Actualizar CRM | `recordId`, `updatedFields` | | Encolar proceso | `jobId`, `queuedAt` | Ejemplo: ```json theme={null} { "ok": true, "status": "success", "data": { "ticketId": "4482", "ticketUrl": "/tickets/ver/4482/", "status": "created" }, "toolName": "create_support_ticket", "safety": "mutation" } ``` > [!WARNING] > > No devuelvas éxito parcial como si fuera éxito final. Si la operación quedó pendiente, usa `status: "queued"` o `status: "pending_confirmation"`. *** ## Checklist antes de entregar un MCP > [!TIP] > > El servidor responde `initialize` correctamente. > [!TIP] > > `tools/list` devuelve todas las tools con `name`, `description` e `inputSchema`. > [!TIP] > > Cada `inputSchema` tiene raíz `type: "object"`. > [!TIP] > > Los campos `object` y `array` se reciben como estructuras reales, no strings JSON. > [!TIP] > > Las tools devuelven `content` con JSON parseable. > [!TIP] > > Los errores tienen `code`, mensaje claro y, si aplica, `field`. > [!TIP] > > Las acciones con side effects devuelven IDs verificables. > [!TIP] > > Las tools con datos faltantes devuelven `needs_more_info` y `nextQuestion`. > [!TIP] > > Las credenciales usan permisos mínimos. > [!TIP] > > Los casos de timeout o sistema externo caído tienen fallback claro. *** ## Ejemplo TypeScript Ejemplo mínimo usando el SDK oficial: ```typescript theme={null} import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; const server = new McpServer({ name: "mi-mcp", version: "1.0.0", }); server.tool( "search_customer", "Busca clientes por teléfono, nombre, usuario o ID externo. When to use: cuando Whaapy necesita identificar al cliente antes de revelar datos. Never use for: confirmar identidad por sí sola.", { query: z.string().describe("Texto de búsqueda"), maxResults: z.number().int().optional().describe("Máximo de resultados"), }, async ({ query, maxResults = 5 }) => { const matches = await searchCustomers(query, maxResults); return { content: [ { type: "text", text: JSON.stringify({ ok: true, status: matches.length > 0 ? "success" : "needs_more_info", data: { matches }, nextQuestion: matches.length === 0 ? "¿Me compartes otro dato para ubicarte?" : undefined, toolName: "search_customer", safety: "read_only", }), }, ], isError: false, }; } ); async function searchCustomers(query: string, maxResults: number) { return []; } ``` *** ## Siguiente paso > **[Configurar MCPs](/guides/agent-mcps)** > > Conecta el MCP al agente desde Whaapy. > **[Configurar Tools](/guides/agent-tools)** > > Define cuándo el agente debe ejecutar cada capacidad. # Integración con n8n Source: https://docs.whaapy.com/integrations/n8n Usa Whaapy con n8n para automatizar flujos que conectan WhatsApp con otras apps ## Resumen El nodo de Whaapy para n8n te permite integrar WhatsApp en flujos externos. Puedes enviar mensajes, gestionar conversaciones, controlar la IA y recibir eventos por webhook. > [!NOTE] > > Si el flujo puede vivir completo dentro de Whaapy, empieza con [Automatizaciones](/concepts/automations). Usa n8n cuando necesites coordinar apps externas, pasos largos o lógica de negocio fuera de Whaapy. ## Instalación ### Desde npm 1. Abre tu instancia de n8n. 2. Ve a **Settings** -> **Community Nodes**. 3. Haz click en **Install a community node**. 4. Ingresa `n8n-nodes-whaapy`. 5. Haz click en **Install**. ### Instalación manual ```bash theme={null} # Ve al directorio de nodos custom de n8n cd ~/.n8n/custom # Instala el paquete npm install n8n-nodes-whaapy # Reinicia n8n ``` ## Autenticación 1. Ve a [app.whaapy.com](https://app.whaapy.com) -> **Settings** -> **API Keys**. 2. Crea una API key nueva. 3. En n8n, ve a **Credentials** -> **New Credential**. 4. Busca **Whaapy API**. 5. Ingresa tu API key. Debe empezar con `wha_`. ## Operaciones disponibles ### Mensajes | Operación | Descripción | | - | - | | Send | Envía un mensaje de WhatsApp: texto, imagen, video, audio, documento, template, interactivo, ubicación, contacto, sticker o reacción | | Retry | Reintenta un mensaje fallido | ### Media | Operación | Descripción | | - | - | | Upload | Sube media al CDN de WhatsApp | ### Conversaciones | Operación | Descripción | | - | - | | List | Obtiene todas las conversaciones | | Get | Obtiene una conversación específica | | Get by Phone | Busca una conversación por teléfono | | Get Messages | Obtiene el historial de mensajes | | Close | Cierra una conversación | | Archive | Archiva una conversación | | Mark Read | Marca la conversación como leída | | Set AI | Activa o desactiva la IA para una conversación | | Pause AI | Pausa la IA temporalmente | | AI Suggest | Obtiene una sugerencia de IA sin enviarla | ### Agente | Operación | Descripción | | - | - | | Toggle | Activa o desactiva la IA globalmente | | Pause | Pausa la IA globalmente durante X minutos | ### Templates | Operación | Descripción | | - | - | | List | Obtiene todos los templates de WhatsApp | | Get | Obtiene un template específico | | Get Variables | Obtiene las variables disponibles del template | | Sync | Sincroniza templates desde Meta | ### Contactos | Operación | Descripción | | - | - | | List | Obtiene todos los contactos | | Get | Obtiene un contacto específico | | Create | Crea un contacto nuevo | | Update | Actualiza un contacto | | Delete | Elimina un contacto | | Search | Busca contactos con filtros avanzados | | Bulk | Ejecuta operaciones en lote | | Merge | Fusiona dos contactos | | Get Tags | Obtiene todas las etiquetas | | Get Fields | Obtiene campos personalizados | ### Funnels | Operación | Descripción | | - | - | | List Stages | Obtiene todas las etapas del funnel | | Get Stage | Obtiene una etapa específica | | Create Stage | Crea una etapa nueva | | Update Stage | Actualiza una etapa | | Delete Stage | Elimina una etapa | | Reorder Stages | Reordena etapas | | Move Contact | Mueve un contacto a una etapa | ## Nodo trigger El nodo **Whaapy Trigger** escucha eventos de webhook: | Evento | Descripción | | - | - | | `message.received` | Mensaje entrante | | `message.sent` | Mensaje enviado | | `message.delivered` | Mensaje entregado | | `message.read` | Mensaje leído | | `message.failed` | Mensaje fallido | | `conversation.created` | Conversación nueva | | `conversation.updated` | Conversación actualizada | | `conversation.handoff` | Handoff a humano | | `*` | Todos los eventos | > [!TIP] > > Este evento solo se dispara cuando se crea una **conversación nueva**: cuando un contacto escribe por primera vez o cuando envías el primer mensaje a un número nuevo desde la API. Para probarlo, usa un número de WhatsApp que nunca haya iniciado una conversación con tu negocio. Si el contacto ya tiene historial, no verás este evento; usa `message.received` para flujos por mensaje. ## Flujos de ejemplo ### Enviar mensaje de bienvenida ``` Webhook (recibe lead) -> Whaapy (envía template) -> Slack (notifica al equipo) ``` ### Auto-respuesta con control de IA ``` Whaapy Trigger (message.received) -> IF (contiene "humano") -> Whaapy (pausa IA) -> Slack (asigna agente) ``` ### Integración con CRM ``` Whaapy Trigger (message.received) -> HTTP Request (CRM API) -> Whaapy (actualiza etiquetas del contacto) ``` ### Automatización de funnel ``` Whaapy Trigger (conversation.created) -> Whaapy (mueve contacto a etapa) -> Whaapy (envía template de bienvenida) ``` ### Automatización del dashboard + n8n ``` Automatización de Whaapy (coincide disparador) -> acción HTTP Request -> Webhook de n8n -> CRM/Slack/Sheets ``` Revisa [Automatizaciones, webhooks y n8n](/guides/automations/webhooks-and-n8n) para decidir cuándo usar cada enfoque. ## Recomendaciones ### Pausar la IA para intervención manual Cuando envíes un mensaje manual, usa la opción **Pause AI** para evitar que la IA responda encima: ```json theme={null} { "to": "+5215512345678", "type": "text", "content": "Yo me encargo personalmente", "additionalFields": { "pauseAi": true, "pauseDuration": 30 } } ``` ### Usar templates Los templates son necesarios para escribir fuera de la ventana de 24 horas. Primero consulta tus templates: 1. Usa **Template -> List** para ver los templates disponibles. 2. Usa el nombre del template en **Message -> Send**. ```json theme={null} { "to": "+5215512345678", "messageType": "template", "templateName": "welcome_message", "templateParameters": ["John", "Acme Corp"] } ``` > [!TIP] > > Por defecto, Whaapy usa los IDs de quick-reply configurados en el template de tu negocio. En el nodo de n8n puedes activar **Allow Button Payload Override** y enviar un mapa JSON en **Quick Reply Payload Overrides** (por ejemplo `{ "0": "confirm_order", "1": "talk_to_agent" }`) solo cuando necesites sobrescribirlos para un flujo específico. > [!TIP] > > Para headers de templates con media, usa **Template Options**: > > * **Header Media Source = URL** + **Header Media URL** para archivos públicos. > * **Header Media Source = Media ID** + **Header Media ID** cuando ya subiste el archivo a Meta. > > Si recibes `MEDIA_PERMISSION_DENIED`, el `media_id` fue subido con otro token, WABA o número distinto al remitente. ### Manejar media 1. Sube el archivo primero con **Media -> Upload**. 2. Usa el `media_id` devuelto en tu mensaje. ## Recursos * [Documentación de la API de Whaapy](/api-reference/overview) * [Documentación de Community Nodes de n8n](https://docs.n8n.io/integrations/community-nodes/) * [Repositorio en GitHub](https://github.com/saymetristan/whaapy-n8n) # Introducción Source: https://docs.whaapy.com/introduction Bienvenido a la API de Whaapy - Automatiza WhatsApp Business de forma simple # Bienvenido a Whaapy API Whaapy es una plataforma que te permite **automatizar WhatsApp Business** de manera simple y poderosa. Con nuestra API puedes enviar mensajes, gestionar conversaciones y crear experiencias increíbles para tus clientes. > [!NOTE] > > **Formato Híbrido**: Nuestra API es compatible con Meta WhatsApp Cloud API, lo que facilita la migración desde otras plataformas. *** ## ¿Qué puedes hacer con Whaapy? > **[Enviar Mensajes](/api-reference/messages/send)** > > Texto, imágenes, videos, documentos, templates, botones y más > **[Automatizar Respuestas](/concepts/ai-agent)** > > Configura un agente de IA con Control, Knowledge, Tools, Subagents y MCPs > **[Crear Automatizaciones](/concepts/automations)** > > Crea reglas para tags, funnels, mensajes, Meta Leads, webhooks y handoff > **[Gestionar Conversaciones](/api-reference/overview)** > > Accede al historial completo de conversaciones > **[Recibir Webhooks](/api-reference/webhooks/overview)** > > Notificaciones en tiempo real de nuevos mensajes *** ## Cómo Funciona ```mermaid theme={null} sequenceDiagram participant App as Tu Aplicación participant Whaapy as Whaapy API participant Meta as Meta/WhatsApp participant User as Usuario App->>Whaapy: POST /messages/v1 Whaapy->>Meta: Enviar mensaje Meta->>User: Mensaje de WhatsApp User-->>Meta: Respuesta Meta-->>Whaapy: Webhook Whaapy-->>App: Webhook (mensaje recibido) ``` *** ## Tipos de Mensajes Soportados Whaapy soporta todos los tipos de mensaje de WhatsApp Business: > **[Texto](/api-reference/messages/send)** > > Mensajes simples > **[Imagen](/api-reference/messages/send-media)** > > JPEG, PNG, WebP > **[Video](/api-reference/messages/send-media)** > > MP4 hasta 16MB > **[Audio](/api-reference/messages/send-media)** > > MP3, OGG, AAC > **[Documento](/api-reference/messages/send-media)** > > PDF, Word, Excel > **[Template](/api-reference/messages/send-templates)** > > Pre-aprobados por Meta > **[Botones](/api-reference/messages/send-interactive)** > > Hasta 3 opciones > **[Listas](/api-reference/messages/send-interactive)** > > Menús desplegables > **[Ubicación](/api-reference/messages/send-location)** > > Coordenadas GPS > **[Contactos](/api-reference/messages/send-contacts)** > > Tarjetas vCard > **[Stickers](/api-reference/messages/send-media)** > > WebP animados > **[Reacciones](/api-reference/messages/send-reactions)** > > Emojis en mensajes *** ## Empezar es Fácil **1. Crea una cuenta** Regístrate en [app.whaapy.com](https://app.whaapy.com/register) y conecta tu número de WhatsApp Business **2. Obtén tu API Key** Ve a **Configuración** → **API Keys** y genera una nueva clave **3. Envía tu primer mensaje** Usa nuestra API para enviar un mensaje de prueba *** ## Ejemplo Rápido Envía tu primer mensaje con una sola llamada: ```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", "content": "¡Hola desde Whaapy!" }' ``` ```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', content: '¡Hola desde Whaapy!' }) }); console.log(await response.json()); ``` ```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', 'content': '¡Hola desde Whaapy!' } ) print(response.json()) ``` > [!TIP] > > El formato simplificado de Whaapy (`content`) te ahorra líneas de código. También soportamos el formato completo de Meta para compatibilidad. *** ## Formato Híbrido Nuestra API acepta **dos estilos** de request: **Estilo Whaapy** Formato simplificado para casos comunes: ```json theme={null} { "to": "+5215512345678", "content": "Hola!" } ``` **Estilo Meta** 100% compatible con WhatsApp Cloud API: ```json theme={null} { "messaging_product": "whatsapp", "to": "+5215512345678", "type": "text", "text": { "body": "Hola!" } } ``` Esto facilita la migración si ya tienes código funcionando con la API oficial de Meta. *** ## ¿Necesitas Ayuda? > **[Documentación](/quickstart)** > > Guía paso a paso > **[API Reference](/api-reference/overview)** > > Referencia completa > **[Soporte](mailto:soporte@whaapy.com)** > > Contacta al equipo *** ## Próximos Pasos > **[Quickstart](/quickstart)** > > Envía tu primer mensaje en 5 minutos > **[Agente IA](/concepts/colme-2)** > > Entiende colme-2 y configura el agente de Whaapy > **[Automatizaciones](/concepts/automations)** > > Diseña flujos automáticos desde el dashboard > **[Autenticación](/api-reference/authentication)** > > Configura tu API Key # Guía rápida Source: https://docs.whaapy.com/quickstart Envía tu primer mensaje en menos de 5 minutos # Guía rápida Esta guía te llevará desde cero hasta enviar tu primer mensaje de WhatsApp en menos de 5 minutos. ## Prerrequisitos > [!TIP] > > Una cuenta en [Whaapy](https://app.whaapy.com/register) > [!TIP] > > Un número de WhatsApp Business conectado > [!TIP] > > Una API Key activa ## Paso 1: Obtén tu API Key 1. Inicia sesión en [app.whaapy.com](https://app.whaapy.com) 2. Ve a **Configuración** → **API Keys** 3. Click en **Generar Nueva Key** 4. Copia tu API Key (formato: `wha_xxxxx...`) > [!WARNING] > > Tu API Key es secreta. No la compartas ni la expongas en código del lado del cliente. ## Paso 2: Envía un Mensaje de Texto El endpoint más simple para enviar un mensaje: ```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", "content": "¡Hola! Este es mi primer mensaje con Whaapy 🎉" }' ``` ```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', content: '¡Hola! Este es mi primer mensaje con Whaapy 🎉' }) }); const data = await response.json(); console.log(data); ``` ```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', 'content': '¡Hola! Este es mi primer mensaje con Whaapy 🎉' } ) print(response.json()) ``` ## Paso 3: Verifica la Respuesta Una respuesta exitosa se ve así: ```json theme={null} { "messaging_product": "whatsapp", "contacts": [{ "wa_id": "5215512345678" }], "messages": [{ "id": "wamid.HBgLNTIxNTUxMjM0NTY3OA==" }], "data": { "message_id": "uuid-del-mensaje", "conversation_id": "uuid-de-conversacion", "status": "sent" } } ``` ## Paso 4: Envía Otros Tipos de Mensajes ### Imagen ```json theme={null} { "to": "+5215512345678", "type": "image", "image": { "link": "https://ejemplo.com/imagen.jpg", "caption": "¡Mira esta imagen!" } } ``` ### Template (para mensajes fuera de ventana de 24h) ```json theme={null} { "to": "+5215512345678", "type": "template", "template": { "name": "hello_world", "language": { "code": "es" } } } ``` ### Documento ```json theme={null} { "to": "+5215512345678", "type": "document", "document": { "link": "https://ejemplo.com/archivo.pdf", "filename": "factura.pdf" } } ``` ## Manejo de Errores Si algo sale mal, recibirás una respuesta con detalles del error: ```json theme={null} { "error": "message_delivery_failed", "message": "La ventana de conversación de 24 horas ha expirado", "code": "131047", "action_required": "Usar template message", "instructions": "Para contactar usuarios después de 24h, envía un template aprobado" } ``` > [!TIP] > > Si recibes error `131047` (ventana cerrada), necesitas enviar un **template message** primero. Los templates están pre-aprobados por Meta y pueden enviarse en cualquier momento. ## Próximos Pasos > **[Autenticación](/api-reference/authentication)** > > Aprende sobre API Keys y scopes > **[Enviar Mensajes](/api-reference/messages/send)** > > Referencia completa del endpoint # Tutorial de Agente IA Source: https://docs.whaapy.com/tutorials/agente-ia Configura, prueba y publica tu agente. # Tutorial de Agente IA Configura, prueba y publica tu agente. 8 capítulos · 20 min. ## Recorrido completo ### Agente IA completo · 20 min ## Capítulos ### 1. Cómo funciona tu agente IA · 2 min ### 2. Instrucciones, reglas y datos · 2 min ### 3. Prepara el conocimiento · 2 min ### 4. Acciones e integraciones · 3 min ### 5. Cuándo usar subagentes · 3 min ### 6. Comportamiento y seguimiento · 3 min ### 7. La IA y tu equipo en Inbox · 3 min ### 8. Prueba, publica y mejora · 3 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Ajustes Source: https://docs.whaapy.com/tutorials/ajustes Lo cotidiano del negocio y las conexiones. # Tutorial de Ajustes Lo cotidiano del negocio y las conexiones. 13 capítulos · 20 min. ## Recorrido completo ### Conexiones completo · 7 min ### Ajustes cotidianos completo · 13 min ## Capítulos ### 1. Ubícate en Ajustes · 1 min ### 2. Tu perfil y tus notificaciones · 2 min ### 3. Datos del negocio y datos para mensajes · 2 min ### 4. Revisa tu conexión de WhatsApp · 2 min ### 5. Configura las etapas del embudo · 1 min ### 6. Campos de contacto y permisos de la IA · 2 min ### 7. Calendario personal y calendario del negocio · 2 min ### 8. Prepara las llamadas con IA · 1 min ### 9. Consulta tu plan y uso · 51 s ### 10. Conecta tus aplicaciones · 2 min ### 11. Recibe contactos con Entradas de leads · 2 min ### 12. Envía avisos a otros sistemas · 2 min ### 13. Administra claves de acceso · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Automatizaciones Source: https://docs.whaapy.com/tutorials/automatizaciones Flujos: disparadores, condiciones, esperas e IA dentro de un flujo. # Tutorial de Automatizaciones Flujos: disparadores, condiciones, esperas e IA dentro de un flujo. 11 capítulos · 23 min. ## Recorrido completo ### Automatizaciones completo · 23 min ## Capítulos ### 1. Entiende y recorre Automatizaciones · 2 min ### 2. Crea tu primer flujo · 2 min ### 3. Decide cuándo empieza · 2 min ### 4. Mensajes y trabajo en equipo · 2 min ### 5. Condiciones, esperas y respuestas · 2 min ### 6. Fechas y seguimiento · 2 min ### 7. Contactos, tareas y calendario · 2 min ### 8. IA dentro de un flujo · 2 min ### 9. Prueba, publica y controla · 2 min ### 10. Ejecuciones y solución de problemas · 2 min ### 11. Reutiliza y conecta · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Calendario Source: https://docs.whaapy.com/tutorials/calendario Citas, disponibilidad, recordatorios y tareas. # Tutorial de Calendario Citas, disponibilidad, recordatorios y tareas. 6 capítulos · 9 min. ## Recorrido completo ### Calendario completo · 9 min ## Capítulos ### 1. Conecta y configura tu calendario · 2 min ### 2. Consulta tu agenda y filtra eventos · 1 min ### 3. Crea una cita paso a paso · 1 min ### 4. Modifica, mueve o cancela una cita · 1 min ### 5. Prepara el agendamiento con IA · 1 min ### 6. Organiza el seguimiento con tareas · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Campañas Source: https://docs.whaapy.com/tutorials/campanas Envíos masivos por lotes, audiencias y resultados. # Tutorial de Campañas Envíos masivos por lotes, audiencias y resultados. 5 capítulos · 9 min. ## Recorrido completo ### Campañas completo · 9 min ## Capítulos ### 1. Encuentra y revisa tus campañas · 1 min ### 2. Prepara un borrador · 1 min ### 3. Configura el mensaje y la audiencia · 2 min ### 4. Elige cómo enviar y confirma · 3 min ### 5. Sigue resultados y gestiona el envío · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Contactos Source: https://docs.whaapy.com/tutorials/contactos Organiza tu base: campos, filtros, segmentos e importación. # Tutorial de Contactos Organiza tu base: campos, filtros, segmentos e importación. 5 capítulos · 6 min. ## Recorrido completo ### Contactos completo · 6 min ## Capítulos ### 1. Encuentra tus contactos · 44 s ### 2. Completa la ficha · 56 s ### 3. Filtros y segmentos · 2 min ### 4. Añade e importa · 1 min ### 5. Trabaja una selección · 1 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Desempeño IA Source: https://docs.whaapy.com/tutorials/desempeno-ia Mide lo que hace la IA y mejórala. # Tutorial de Desempeño IA Mide lo que hace la IA y mejórala. 4 capítulos · 6 min. ## Recorrido completo ### Desempeño IA completo · 6 min ## Capítulos ### 1. Conoce Desempeño IA · 1 min ### 2. Interpreta los resultados · 2 min ### 3. Investiga con el Observador · 1 min ### 4. Configura y mejora la atención · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Equipo Source: https://docs.whaapy.com/tutorials/equipo Personas, roles, permisos y reparto de contactos. # Tutorial de Equipo Personas, roles, permisos y reparto de contactos. 5 capítulos · 10 min. ## Recorrido completo ### Equipo completo · 10 min ## Capítulos ### 1. Conoce a tu equipo · 2 min ### 2. Invita a una persona · 1 min ### 3. Configura accesos y participación · 2 min ### 4. Configura el reparto de contactos · 3 min ### 5. Retira a una persona sin perder el seguimiento · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Inbox Source: https://docs.whaapy.com/tutorials/inbox Atiende conversaciones, asigna, etiqueta y responde más rápido. # Tutorial de Inbox Atiende conversaciones, asigna, etiqueta y responde más rápido. 5 capítulos · 5 min. ## Recorrido completo ### Inbox completo · 5 min ## Capítulos ### 1. Conoce tu Inbox · 46 s ### 2. Encuentra y responde · 1 min ### 3. Organiza la atención · 56 s ### 4. Trabaja con la IA · 59 s ### 5. Da seguimiento y cierra · 1 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Meta Business Source: https://docs.whaapy.com/tutorials/meta Tu cuenta de Meta: negocio, accesos, verificación, método de pago y cuentas restringidas. # Tutorial de Meta Business Tu cuenta de Meta: negocio, accesos, verificación, método de pago y cuentas restringidas. 6 capítulos · 15 min. ## Videos ### 1. Meta Business y WhatsApp · 2 min ### 2. Negocio y accesos · 2 min ### 3. Verificación del negocio (México) · 3 min ### 4. Método de pago de WhatsApp · 3 min ### 5. Número y conexión · 2 min ### 6. Cuenta restringida o pendiente · 3 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Activación Source: https://docs.whaapy.com/tutorials/onboarding Conecta tu número, resuelve los requisitos de Meta y prueba que recibes y respondes. # Tutorial de Activación Conecta tu número, resuelve los requisitos de Meta y prueba que recibes y respondes. 8 capítulos · 13 min. ## Recorrido completo ### Activación completa · 11 min ## Capítulos ### 1. Prepara tu activación · 1 min ### 2. Conecta un número nuevo · 1 min ### 3. Conserva tu app o migra tu número · 1 min ### 4. Resuelve los requisitos de Meta · 2 min ### 5. Prueba que recibes y respondes · 2 min ### 6. Prepara tu agente y tu equipo · 1 min ### 7. Retoma pendientes y recupera tu canal · 1 min ### 8. Activa un cliente desde tu agencia · 2 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**. # Tutorial de Plantillas Source: https://docs.whaapy.com/tutorials/plantillas Plantillas de Meta y respuestas rápidas. # Tutorial de Plantillas Plantillas de Meta y respuestas rápidas. 5 capítulos · 6 min. ## Recorrido completo ### Plantillas completo · 6 min ## Capítulos ### 1. Encuentra una plantilla · 1 min ### 2. Crea y envía a revisión · 2 min ### 3. Configura las variables · 1 min ### 4. Crea una respuesta rápida · 1 min ### 5. Úsalas en Inbox y Campañas · 1 min > [!NOTE] > > Los mismos videos están dentro de Whaapy: menú **Ayuda** de cada pantalla → «Tutorial de …», o el **Centro de ayuda**.