smsenlinea.com API
Bienvenido a la documentación de la API de smsenlinea.com. Con nuestra API RESTful, puedes integrar mensajería de WhatsApp en tu aplicación, gestionar conversaciones y automatizar flujos de comunicación.
Características Principales
Asignación y Ruteo de conversaciones
Cómo el sistema decide a qué asesor se asigna una conversación entrante, y cómo configurarlo desde el panel y la API.
Departamentos
Un departamento (ej. Ventas, Soporte) agrupa asesores y tiene palabras clave para detectar la intención del cliente. Un asesor puede pertenecer a varios departamentos. Se configuran en Asesor IA → editar, y cada asesor elige sus departamentos en /admin/agents.
Estrategias de asignación
Campo assignment_strategy del departamento. La rotación ocurre solo entre los asesores de ese departamento:
| Valor | Comportamiento |
|---|---|
least_busy | Por disponibilidad — el asesor con menos chats abiertos (prioriza en línea). Default. |
round_robin | Secuencial — uno tras otro en cola circular (1º, 2º, … y vuelve al primero). |
rotate_available | Combinada — rotación secuencial solo entre los asesores en línea (heartbeat < 3 min). |
Seleccionar la estrategia vía API
POST /api/v1/departments
PUT /api/v1/departments/{id}
Authorization: Bearer <API_KEY> (scope departments:write)
Content-Type: application/json
{
"name": "Ventas",
"keywords": ["comprar", "precio", "cotización"],
"assignment_strategy": "round_robin"
}
Asignar/quitar asesores de un departamento:
POST /api/v1/departments/{id}/agents { "user_id": 5, "is_primary": true }
DELETE /api/v1/departments/{id}/agents/{userId}
Ruteo por intención
Al escalar a humano, se elige departamento por: 1) palabra clave del mensaje, y 2) clasificación por IA (si handoff_rules_json.ai_routing = true). Resuelto el departamento, se aplica su estrategia de asignación.
Código de asesor directo + enlace wa.me
Cada asesor tiene un código único (users.direct_code). Si el primer mensaje del cliente empieza con asesor:CODE (también asesor: CODE o asesor#CODE), el chat se asigna directo a ese asesor y se pausa el bot.
https://wa.me/<NUMERO_DEL_NEGOCIO>?text=asesor:<CODE>
Cada asesor copia su enlace desde el menú del chat → "Mi enlace directo de contacto".
Widget público (burbuja de WhatsApp)
La burbuja usa la API pública (la public_key va en el path, auth widgetAuth):
| Endpoint | Descripción |
|---|---|
GET /api/v1/widget/{public_key}/config | Config + departamentos + asesores (id, nombre, avatar — sin datos sensibles). |
POST /api/v1/widget/{public_key}/lead | Lead con department_id y preferred_agent_id (el cliente elige depto/asesor). |
POST /api/v1/widget/{public_key}/event | Tracking (bubble_opened, form_submitted, …). |
Relación con el ruteo: departamento elegido → estrategia del departamento; preferred_agent_id o código asesor:CODE → asesor directo; sin elección → ruteo por intención. Las widget keys se gestionan en /admin/integrations/wordpress/widget-keys.
tenants.settings.bot_enabled. Si está en false, el Asesor IA no atiende ninguna conversación (solo humanos). Se controla desde el menú del chat → "Asesor IA (bot)".
Inicio Rápido
Comienza a usar la API de smsenlinea.com en 5 minutos.
1. Crear una Clave API
Dirígete a Configuración → Claves API y haz clic en "Nueva Clave". Selecciona los permisos que necesites.
2. Primer Llamado
Usa tu clave API para hacer un llamado HTTP simple:
curl -X GET "https://panel.smsenlinea.com/api/v1/external/conversations" \
-H "X-API-Key: whasaas_live_..." \
-H "Content-Type: application/json"
3. Manejar Respuestas
Todas las respuestas son JSON. Las respuestas exitosas retornan 200 OK:
{
"ok": true,
"data": {
"conversations": [
{
"id": "conv_123",
"phone": "573001234567",
"status": "active",
"created_at": "2026-05-22T14:30:00Z"
}
]
}
}
4. Configurar Webhooks
Para recibir notificaciones en tiempo real, configura un webhook en Integraciones → Webhooks.
Autenticación
smsenlinea.com soporta dos métodos de autenticación para la API:
Al generar una clave la verás una única vez: guárdala en un lugar seguro. Nosotros solo almacenamos una huella criptográfica, de modo que ni nosotros podemos recuperarla. Si la pierdes o crees que se filtró, genera una nueva y revoca la anterior desde Ajustes → Developer API Keys. En el panel solo verás su principio y sus últimos dígitos, para reconocerla.
Comprueba con qué cuenta estás hablando
Toda respuesta de la API incluye tres encabezados que identifican la cuenta a la que pertenece la clave usada:
X-Account-Id: 46
X-Account-Name: Compuservices Jr
X-Api-Key-Name: Clientes Compuservices
Son la forma más rápida de detectar el error más común al integrar: usar la clave de otra cuenta. Si ves datos que no esperabas —conversaciones, contactos o números que no reconoces—, mira primero X-Account-Name: la API siempre devuelve lo que pertenece a la cuenta dueña de la clave, así que un nombre distinto al tuyo significa que la clave configurada no es la correcta, no que estés viendo datos ajenos.
También puedes consultar GET /api/v1/account para ver la cuenta completa, su plan y su consumo.
Método 1: API Key Header
Envía tu clave API en el encabezado X-API-Key:
GET /api/v1/external/conversations HTTP/1.1
Host: panel.smsenlinea.comX-API-Key: whasaas_live_1a2b3c4d5e6f7g8h
Content-Type: application/json
Método 2: Bearer Token
Alternativamente, usa un Bearer token en el encabezado Authorization:
GET /api/v1/external/conversations HTTP/1.1
Host: panel.smsenlinea.comAuthorization: Bearer whasaas_live_1a2b3c4d5e6f7g8h
Content-Type: application/json
Permisos (Scopes)
Cada clave API tiene permisos específicos. Asigna sólo los que tu integración necesita — principio de menor privilegio. Hay 33 scopes disponibles, organizados por categoría:
📨 Mensajería (messages)
| Scope | Permite |
|---|---|
messages:read | Listar y consultar mensajes; verificar números en WhatsApp |
messages:write | Enviar mensajes (texto, media, audio, documento, botones, lista, encuesta, ubicación, contacto, reacción, forward, bulk) |
messages:edit | Editar el contenido de un mensaje enviado |
messages:delete | Eliminar mensajes para todos (recall) |
💬 Conversaciones (conversations)
| Scope | Permite |
|---|---|
conversations:read | Listar y consultar conversaciones, notas y eventos |
conversations:write | Crear/modificar conversaciones, archivar, marcar leído, etiquetar |
conversations:assign | Asignar y transferir conversaciones a asesores |
conversations:update_status | Cambiar estado (open / pending / resolved / archived) |
conversations:note | Agregar notas internas a conversaciones |
📱 Instancias de WhatsApp (instances)
| Scope | Permite |
|---|---|
instances:read | Listar, ver y consultar estado / estadísticas de instancias |
instances:write | Crear y eliminar instancias |
instances:connect | Generar QR, conectar y reiniciar instancias |
instances:disconnect | Desconectar / cerrar sesión de WhatsApp |
👥 Contactos & Asesores
| Scope | Permite |
|---|---|
contacts:read | Listar contactos, listas, foto de perfil |
contacts:write | Crear/editar/eliminar/bloquear contactos; importar CSV |
agents:read | Listar asesores, ver su disponibilidad y estadísticas |
🛒 Comercio (products / orders)
| Scope | Permite |
|---|---|
products:read | Consultar catálogo de productos y categorías |
products:write | Crear/modificar productos, variantes, imágenes y categorías |
orders:read | Listar órdenes y descargar recibos |
orders:write | Crear órdenes, cambiar estado, refunds, enviar link de pago |
📣 Marketing & Plantillas
| Scope | Permite |
|---|---|
campaigns:read | Consultar campañas masivas y sus destinatarios |
campaigns:write | Crear, programar, iniciar/pausar campañas (requiere tier ≥ Starter) |
templates:read | Leer plantillas y respuestas rápidas |
templates:write | Crear y modificar plantillas y respuestas rápidas |
🏷️ Organización (labels)
| Scope | Permite |
|---|---|
labels:read | Listar etiquetas del tenant |
labels:write | Crear, editar y eliminar etiquetas |
📦 Archivos (media)
| Scope | Permite |
|---|---|
media:read | Descargar archivos multimedia |
media:write | Subir archivos para usar en envíos |
🪝 Webhooks & Reportes
| Scope | Permite |
|---|---|
webhooks:read | Listar webhooks y ver entregas |
webhooks:write | Crear y editar webhooks; lanzar tests |
webhooks:manage | Eliminar webhooks y reintentar entregas |
analytics:read | Acceder a métricas, reportes y dashboards |
account:read | Información del tenant: plan, vencimiento, cuotas, rate-limit |
🧾 Cotizaciones y Facturas (quotes / invoices)
| Scope | Permite |
|---|---|
quotes:read | Consultar cotizaciones: listado, detalle, timeline y estadísticas (enviadas/leídas/aceptadas) |
invoices:read | Consultar facturas: listado, detalle, pagos y estadísticas de cartera |
tickets:read | Consultar tickets, órdenes de trabajo, plantillas y estadísticas |
tickets:write | Crear tickets/órdenes, responder, cambiar estado y asignar |
reminders:read | Consultar recordatorios recurrentes y su historial de envíos |
reminders:write | Crear, editar, pausar/reanudar y eliminar recordatorios recurrentes |
Ambos requieren además que el módulo correspondiente esté incluido en tu plan; de lo contrario la API responde 403 plan_feature_required.
quotes:read / invoices:read) y recibes 403 insufficient_scope, edita o rota la llave para incluirlos.
IP Whitelist
Para mayor seguridad, puedes restringir el acceso a IPs específicas. Configura esto desde la página de detalles de tu clave API.
Catálogo de Endpoints
smsenlinea.com API v2 expone más de 120 endpoints activos bajo el prefijo /api/v1, más los 9 endpoints legacy bajo /api/v1/external. Todos requieren autenticación con API Key.
/api/v1/... y devuelven el formato {ok, data, message}. Los legacy bajo /api/v1/external/... mantienen el formato {success, data, message} por compatibilidad.
📱 Instancias de WhatsApp
Scope base: instances:read / instances:write / instances:connect / instances:disconnect
Lista todas las instancias del tenant.
Detalle completo de una instancia.
Crea una nueva instancia y devuelve el QR de conexión.
Refresca el QR para conectar la instancia.
Estado actual (open / connecting / disconnected) + teléfono.
Reconectar la instancia (genera QR fresco).
Cierra la sesión de WhatsApp.
Reinicia el proceso de Evolution para esa instancia.
Elimina la instancia (también en Evolution).
Métricas: mensajes enviados/recibidos, conversaciones activas (periodos: 1h, 24h, 7d, 30d).
📨 Mensajes
Scope base: messages:read / messages:write / messages:edit / messages:delete
Envío
instance_id (número) o instance_name (texto) en el body para elegir la instancia de WhatsApp. Si no lo envías, se usa la primera instancia conectada de tu cuenta. Con más de una instancia, especifícala siempre. Ver la guía completa en Selección de instancia.
Envía un mensaje de texto. Body: {phone, message, instance_id?, instance_name?}.
Envía imagen, video, audio, documento o sticker. Body: {phone, media_url, media_type, caption?, filename?, mime_type?}. media_type admite image, video, audio, document y sticker (también los alias ptt, voice, gif, photo). En media_url puedes pasar una URL pública o el archivo en base64.
Envía un audio / nota de voz. Body: {phone, audio_url}.
Envía un documento (PDF, DOC, XLSX, etc.).
Multimedia en números oficiales de Meta
En una instancia meta_cloud el envío se comprueba antes de
llamar a WhatsApp, así que un fallo llega como 422 explicando qué corregir
en vez de un error genérico del proveedor:
| Código | Cuándo |
|---|---|
outside_24h_window | El contacto no te ha escrito en las últimas 24 h. Fuera de esa ventana WhatsApp solo permite plantillas aprobadas: usa send-template. |
unsupported_media_type | El formato no está en la lista que admite WhatsApp para ese tipo. |
media_too_large | El archivo supera el límite (solo se comprueba cuando lo mandas en base64). |
Formatos y tamaños que acepta WhatsApp Cloud API:
| Tipo | Formatos | Máximo |
|---|---|---|
| image | image/jpeg, image/png | 5 MB |
| video | video/mp4, video/3gp | 16 MB |
| audio | audio/aac, amr, mpeg, mp4, ogg (opus) | 16 MB |
| sticker | image/webp | 500 KB animado / 100 KB estático |
| document | cualquiera | 100 MB |
Estos límites son de WhatsApp, no del panel. En instancias Evolution (números por QR) no se aplica ninguna de estas comprobaciones.
Archivos que recibes: media_url caduca en 24 h
Cuando alguien te envía un archivo, el panel lo descarga y lo guarda. La
media_url que ves en el webhook message.received y en
GET /api/v1/messages es un enlace firmado que expira a las 24 horas.
No la guardes como si fuera permanente.
Tienes dos formas de trabajar con ella:
- Descárgate el archivo al recibir el webhook y guárdalo en tu sistema. Es lo más robusto.
- Renueva el enlace cuando lo necesites con el
media_keyque acompaña a cada mensaje:GET /api/v1/media/url?key=EL_MEDIA_KEYdevuelve una URL nueva.
GET /api/v1/messages y GET /api/v1/messages/{id} firman el
enlace en cada lectura, así que la media_url que te devuelven
siempre viene recién emitida.
Ojo con HEAD: el enlace se firma para GET.
Una petición HEAD responde 403 aunque el enlace siga vigente —
si compruebas disponibilidad, hazlo con GET.
// message.received con archivo
{
"event": "message.received",
"data": {
"type": "image",
"media_url": "https://...s3...?X-Amz-Expires=86400", // caduca en 24 h
"media_key": "tenants/46/media/media_6a8c7b6e.jpg", // permanente
"media_mime": "image/jpeg",
"media_filename": "foto.jpg",
"media_size": 26388
}
}
Envía ubicación geográfica. Body: {phone, latitude, longitude, name?, address?}.
Envía una tarjeta de contacto (vCard).
Envía una plantilla local con variables {{var}}.
Mensaje con botones de respuesta rápida. Body: {phone, body, buttons[], footer?, header?}. Funciona con los dos tipos de número.
En WhatsApp oficial (Meta): máximo 3 botones, título de 20 caracteres, cuerpo de 1024 y pie de 60. Si envías de más, se recortan a esos límites en lugar de fallar. Requiere la ventana de 24 horas abierta.
curl -X POST https://panel.smsenlinea.com/api/v1/messages/send-buttons \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{"phone":"573001234567","body":"¿Confirmas tu cita?",
"buttons":[{"id":"si","title":"Sí"},{"id":"no","title":"No"}]}'
Lista interactiva con secciones. Body: {phone, body, button_text, sections[], footer?, title?}. Funciona con los dos tipos de número.
En WhatsApp oficial (Meta): hasta 10 secciones y 10 filas en total entre todas; título de fila 24 caracteres y descripción 72. Lo que sobre se recorta. Requiere la ventana de 24 horas abierta.
Este endpoint hace dos cosas distintas según lo que envíes:
template_name→ plantilla aprobada de WhatsApp (Meta). Es la única forma de escribir fuera de la ventana de 24 horas, y admite encabezado, botones y carrusel.template_id→ plantilla de texto guardada en tu panel, que se envía como mensaje normal.
Paso 1: consulta qué necesita la plantilla. GET /api/v1/instances/{id}/templates devuelve cada plantilla con un bloque requires:
{
"name": "promo_agosto", "language": "es", "status": "APPROVED",
"requires": {
"header": { "type": "IMAGE", "vars": 0, "needs_media": true },
"body_vars": 2,
"buttons": [
{ "index": 0, "type": "URL", "needs_value": true,
"hint": "Parte final de la URL (se añade a https://tienda.com/)" },
{ "index": 1, "type": "QUICK_REPLY", "needs_value": false }
],
"carousel": { "cards": 0 }
}
}
Paso 2: envíala con esos datos.
curl -X POST https://panel.smsenlinea.com/api/v1/messages/send-template \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{
"phone": "573001234567",
"instance_id": 50,
"template_name": "promo_agosto",
"language": "es",
"header": "https://midominio.com/banner.jpg",
"variables": ["Ana", "20%"],
"buttons": { "0": "oferta-agosto" }
}'
Cómo se rellena cada parte:
| Campo | Para qué |
|---|---|
variables | Valores del cuerpo, en orden ({{1}}, {{2}}…) |
header | URL del archivo si el encabezado es imagen, vídeo o documento; el texto si es de tipo texto |
buttons | Valor por índice de botón. Solo lo piden los de URL dinámica y los de copiar código; los de respuesta rápida lo admiten como identificador opcional |
cards | Carrusel: por tarjeta, {header, variables, buttons} |
Si falta algún dato, la API responde 422 diciendo exactamente qué falta, sin llegar a gastar el envío. Meta rechaza el mensaje entero cuando el número de parámetros no coincide, así que se comprueba antes.
Errores propios: template_not_approved (no existe o Meta no la aprobó), not_supported (intentaste una plantilla aprobada en un número por QR) y empty_template.
Redacta una plantilla con IA a partir de una descripción en lenguaje normal,
respetando las reglas de Meta. Devuelve el borrador ya validado; no lo crea en Meta:
lo revisas y después lo envías con POST /instances/{id}/templates.
curl -X POST https://panel.smsenlinea.com/api/v1/instances/50/templates/ai \\
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \\
-d '{
"descripcion": "Avisar al cliente que su equipo ya está reparado y puede pasar a recogerlo, con el número de orden y el horario",
"negocio": "Compuservices JR",
"con_botones": true
}'
Respuesta:
{
"template": {
"name": "equipo_reparado_listo",
"category": "UTILITY",
"language": "es",
"body": "Hola {{1}}, te informamos que tu equipo con orden {{2}} ya está reparado...",
"body_examples": ["Carlos", "A-123"],
"header_type": "TEXT", "header_text": "Tu equipo está listo",
"footer": "Compuservices JR",
"buttons": [],
"explicacion": "Por qué eligió esa categoría y qué representa cada variable"
},
"ready": true,
"errors": [],
"warnings": [],
"fixes": ["Ajusté el nombre a ..."]
}
| Campo | Para qué |
|---|---|
descripcion | Obligatorio. Qué quieres decir, en tus palabras |
negocio | Nombre del negocio: nombrarlo reduce rechazos |
categoria | Sugerencia. Si el texto no encaja, la IA elige la correcta y lo explica |
con_botones | Si false, no propone botones |
ready | Léelo antes de enviar: true = pasó todas las reglas |
warnings | Cosas que la API acepta pero al revisor no le gustan (categoría dudosa, marketing sin opción de baja…) |
fixes | Retoques que se aplicaron solos (nombre, numeración de variables…) |
Redacta con DeepSeek. Si tienes tu propia clave de DeepSeek en Ajustes → Inteligencia Artificial se usa esa; si no, la de la plataforma. Consume saldo de IA de tu cuenta.
Validación previa al crear plantillas
POST /instances/{id}/templates comprueba la plantilla antes de mandarla a Meta
y responde 422 con la lista exacta de lo que falla. Meta tarda hasta 24 h en revisar,
así que un fallo de forma que se detecta aquí te ahorra un día.
Reglas comprobadas contra la API de Meta:
| Meta RECHAZA | Meta ACEPTA |
|---|---|
|
Variable al principio o al final del cuerpo Variables no correlativas ( {{1}} y {{3}})Número de ejemplos distinto del de variables Cuerpo formado solo por variables Más de dos saltos de línea seguidos Más de 1 variable en el encabezado Cualquier variable en el pie |
Dos variables seguidas: Hola {{1}} {{2}}(se suele creer lo contrario, pero la API las admite) |
Envía un formulario nativo de WhatsApp: el cliente lo rellena
dentro del chat, sin abrir un navegador ni escribir sus datos en varios mensajes. Cuando lo envía,
la respuesta llega a tu webhook como evento flow.response y aparece en el chat.
Solo en WhatsApp oficial (Meta). En números conectados por código QR
no existe: usa /messages/send-buttons o /messages/send-list.
Paso 1: crea el formulario (o hazlo desde el panel, en Formularios de WhatsApp). Se crea como borrador.
curl -X POST https://panel.smsenlinea.com/api/v1/instances/50/flows \\
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \\
-d '{ "preset": "cita" }'
Modelos listos: cita, cotizacion, soporte,
encuesta, registro. O defínelo campo a campo:
{
"name": "Agendar visita",
"title": "Agendar visita",
"submit_label": "Confirmar",
"categories": ["APPOINTMENT_BOOKING"],
"fields": [
{ "type": "text", "name": "nombre", "label": "Tu nombre", "required": true },
{ "type": "date", "name": "fecha", "label": "Fecha preferida", "required": true },
{ "type": "select", "name": "franja", "label": "Franja horaria", "required": true,
"options": [ {"id":"am","title":"Mañana"}, {"id":"pm","title":"Tarde"} ] }
]
}
Tipos de campo: text, textarea, email,
phone, number, date, select, radio,
checkbox, optin. Máximo 20 campos.
Paso 2: publicícalo. Un borrador no se puede enviar, y uno publicado ya no se puede modificar (se crea otro).
curl -X POST https://panel.smsenlinea.com/api/v1/instances/50/flows/FLOW_ID/publish \\
-H "Authorization: Bearer TU_API_KEY"
Paso 3: envíalo.
curl -X POST https://panel.smsenlinea.com/api/v1/messages/send-flow \\
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \\
-d '{
"phone": "573001234567",
"instance_id": 50,
"flow_id": "1543663357804290",
"body": "Agenda tu visita técnica en menos de un minuto.",
"button_text": "Agendar visita"
}'
| Campo | Para qué |
|---|---|
body | Texto del mensaje que acompaña al botón |
button_text | Texto del botón. Máximo 30 caracteres y sin emojis (se limpian solos) |
flow_token | Tu identificador del envío. WhatsApp te lo devuelve en la respuesta, y es lo que permite saber a qué envío corresponde. Si no lo mandas, se genera y se devuelve |
screen | Pantalla inicial. Por omisión, la del formulario |
data | Datos con los que abrir esa pantalla (opcional) |
footer, header | Pie y encabezado de texto (opcionales) |
Errores propios: flow_not_found,
flow_not_published (sigue en borrador), missing_screen y not_supported
(lo intentaste en un número por QR). Se comprueban antes de gastar el envío.
Lista tus formularios. Añade ?sync=1 para refrescarlos contra Meta
(por si los creaste en WhatsApp Manager). El campo sendable te dice de un vistazo cuáles
puedes enviar ya.
{
"flow_id": "1543663357804290",
"name": "Agendar cita",
"status": "PUBLISHED",
"initial_screen": "FORMULARIO",
"responses_count": 42,
"sendable": true,
"preview_url": "https://business.facebook.com/wa/manage/flows/..."
}
Las respuestas que han enviado tus clientes. Filtra con
flow_id, conversation_id o since.
{
"id": 128,
"flow_id": "1543663357804290",
"flow_name": "Agendar cita",
"flow_token": "ft_9a3c...",
"conversation_id": 1126,
"from": "573001234567",
"answers": { "nombre": "María Ríos", "fecha": "1755734400000", "franja": "am" },
"created_at": "2026-08-21 15:04:22"
}
Las respuestas van tal cual las manda WhatsApp:
las fechas en milisegundos desde epoch y las listas por el id de la opción, no por su texto.
Es a propósito, para no alterar el dato original. En el panel se muestran ya traducidas.
Borra el formulario si es un borrador. Si ya estaba publicado Meta no permite borrarlo: se retira de circulación y deja de poder enviarse.
Botón que abre un enlace. En vez de pegar la URL en el texto, el cliente ve un botón con la etiqueta que elijas. Body: {phone, body, url, button_text?, footer?, header?}.
curl -X POST https://panel.smsenlinea.com/api/v1/messages/send-cta \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{"phone":"573001234567",
"body":"Tu cotización COT-2026-0071 está lista.",
"url":"https://panel.smsenlinea.com/q/abc123",
"button_text":"Ver cotización"}'
Solo en WhatsApp oficial (Meta) y dentro de la ventana de 24 horas. La etiqueta del botón admite 20 caracteres. La URL debe empezar por http:// o https://; si no, responde 422 indicando el campo. En números por QR usa /messages/send con la URL en el texto.
Pide la ubicación con un botón: el cliente la comparte con un toque, sin explicarle cómo adjuntarla. Útil para visitas técnicas, entregas y órdenes de trabajo. Body: {phone, body}. Solo en WhatsApp oficial (Meta).
Encuesta de WhatsApp. Body: {phone, question, options[], multiple_answers?}. Solo en números conectados por QR: la API oficial de Meta no tiene encuestas y la llamada responde not_supported.
Reacciona con emoji a un mensaje existente. Body: {evolution_msg_id, emoji}.
Reenvía un mensaje a múltiples destinatarios.
Envío en lote (hasta 500 mensajes con delay configurable). Requiere tier ≥ Starter.
Lectura y gestión
Lista mensajes con filtros: conversation_id, instance_id, direction, type, date_from, date_to, paginación.
Detalle completo de un mensaje.
Edita un mensaje enviado. Body: {evolution_msg_id, content}. Solo en números conectados por QR: la API oficial de Meta no permite editar mensajes.
Elimina un mensaje para todos (recall). Solo en números conectados por QR: la API oficial de Meta no permite eliminar mensajes ya enviados.
Qué puede hacer cada tipo de número
| Acción | Por QR | Oficial (Meta) |
|---|---|---|
| Texto, imagen, audio, documento, ubicación, contacto | Sí | Sí |
| Plantillas aprobadas | — | Sí (obligatorias fuera de 24 h) |
| Plantillas con encabezado, botones y carrusel | — | Sí |
| Formularios dentro de WhatsApp (Flows) | — | Sí |
| Botones y listas interactivas | Sí | Sí (con límites de Meta) |
Botón que abre un enlace (send-cta) | No | Sí |
| Pedir la ubicación con un botón | No | Sí |
| Frases de inicio y comandos del número | No | Sí |
| Reacciones, reenviar | Sí | Sí |
| Acuse de lectura (✓✓ azul) y “escribiendo…” | Sí | Sí |
| Encuestas | Sí | No — no existe en la API de Meta |
| Editar / eliminar un mensaje enviado | Sí | No — no existe en la API de Meta |
| Ver si el cliente está en línea o escribiendo | Sí | No — Meta no publica ese dato |
Cuando una acción no existe en el número oficial, la API responde 422 con el código not_supported y una explicación, en lugar de fallar de forma silenciosa.
Marca un mensaje recibido como leído.
Helpers WhatsApp
Verifica qué números tienen cuenta de WhatsApp. Body: {numbers: []}.
URL de foto de perfil de un contacto.
💬 Conversaciones
Scope base: conversations:read / conversations:write / conversations:assign / conversations:update_status / conversations:note
Lista conversaciones. Filtros: status, assigned_to (id o "unassigned"), instance_id, unread_only, search.
Detalle de una conversación con contact, instance, assigned_to.
Mensajes paginados de una conversación.
Inicia una conversación nueva. Body: {phone, instance_id?, initial_message?}.
Asigna a uno o varios asesores. Body: {conversation_id|phone, agent_id|agent_email|agent_ids[]}. Pasa null para desasignar.
Cambia estado: open, pending, resolved, archived.
Agrega nota interna a una conversación.
Lista todas las notas internas de la conversación.
Agrega una etiqueta. Body: {label_id}.
Quita una etiqueta.
Archiva o desarchiva. Body: {archived: true|false}.
Marca todos los mensajes entrantes como leídos y avisa a WhatsApp, de modo que el cliente ve los ✓✓ azules en su teléfono. La respuesta incluye whatsapp_notified: si es false, el marcado se hizo solo en tu panel (por ejemplo si la línea estaba desconectada).
Muestra “escribiendo…” al cliente. Cuerpo: {"state": "typing"} — valores admitidos typing, recording (grabando audio) y paused (dejar de mostrarlo).
curl -X POST https://panel.smsenlinea.com/api/v1/conversations/1234/typing \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"state":"typing"}'
Diferencias por proveedor. En números conectados por QR el indicador se activa y se apaga cuando tú quieras. En WhatsApp oficial (Meta) el indicador se envía respondiendo al último mensaje del cliente, expira solo a los ~25 segundos o al enviar tu respuesta, y solo funciona dentro de la ventana de atención de 24 horas: por eso paused se acepta pero no hace nada, y si el cliente nunca te ha escrito la llamada responde no_inbound_message.
Devuelve las frases de inicio y los comandos configurados en el número.
Configura ambos. Son ajustes del número, no mensajes:
- Frases de inicio: hasta 4 sugerencias que se pueden tocar, visibles para quien abre el chat por primera vez. 80 caracteres, sin emojis.
- Comandos: hasta 30, se ofrecen cuando el cliente escribe «/». Nombre de 32 caracteres, descripción de 256, sin emojis.
curl -X POST https://panel.smsenlinea.com/api/v1/instances/50/automation \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{
"prompts": ["Pedir una cotización", "Ver catálogo", "Hablar con un asesor"],
"commands": [
{"command_name":"cotizar","command_description":"Solicitar una cotización"},
{"command_name":"soporte","command_description":"Hablar con un asesor"}
]
}'
Lo que exceda los límites se recorta en lugar de fallar, porque Meta rechaza la configuración entera si algo se pasa. Solo en WhatsApp oficial (Meta).
Estado en línea de la línea completa (no de un chat). Cuerpo: {"state": "online"} u offline. Estar online es además lo que permite recibir la presencia de tus contactos. Solo aplica a conexiones por QR: en WhatsApp oficial no existe ese concepto y la respuesta llega con applied: false.
Activa o pausa el chatbot en esta conversación.
Elimina (soft-delete: archiva).
📜 API Legacy /api/v1/external/*
Estos endpoints siguen funcionando indefinidamente para no romper integraciones existentes. Para nuevas integraciones se recomienda usar los endpoints v2 de arriba.
⏱️ Mensajes programados
Scope: messages:schedule o messages:write. Cola central con throttle anti-baneo por instancia.
Encola un mensaje diferido. Body: {phone, message|media_url, instance_id?|instance_name?, send_after?, dedupe_key?}.
Estado del mensaje encolado.
Cancela un mensaje aún no enviado.
🧑💼 Asesores y Departamentos
Scope: agents:read / departments:read / departments:write
Lista los asesores del tenant (nombre, email, rol, estado).
Detalle de un asesor.
Métricas del asesor: conversaciones atendidas, mensajes, tiempos de respuesta.
Disponibilidad actual del asesor.
Conversaciones asignadas al asesor.
Lista departamentos. CRUD completo con POST/PUT/DELETE en /departments/{id} y gestión de miembros en /departments/{id}/agents.
👥 Contactos y Listas
Scope: contacts:read / contacts:write
Lista contactos con búsqueda y paginación.
Busca un contacto por teléfono.
Crea un contacto. También: POST /contacts/update (upsert por teléfono), POST /contacts/import, GET /contacts/export, block/unblock, y listas en /contact-lists.
🧾 Cotizaciones NUEVO
Scope: quotes:read · Requiere el módulo de Cotizaciones en tu plan (Pro/Enterprise). Solo lectura: consulta el funnel completo — enviadas, leídas, aceptadas, rechazadas, vencidas.
Lista cotizaciones. Filtros: status (draft, sent, viewed, accepted, rejected, expired, cancelled), date_from, date_to, search (número/cliente), paginación. Cada fila incluye views_count, first_viewed_at, acceptance y public_url.
Detalle completo: ítems, opciones (good/better/best), aceptación (quién y cuándo) y events — el timeline completo (created, sent_wa, viewed, accepted...).
Funnel agregado: by_status, sent_count, viewed_count, accepted_count, view_rate, acceptance_rate, accepted_amount.
// GET https://panel.smsenlinea.com/api/v1/quotes/stats
{
"ok": true,
"data": {
"total": 42,
"by_status": {"sent": 10, "viewed": 18, "accepted": 9, "rejected": 3, "draft": 2},
"sent_count": 40,
"viewed_count": 27,
"accepted_count": 9,
"view_rate": 0.675,
"acceptance_rate": 0.225,
"accepted_amount": 18500000
}
}
💵 Facturas NUEVO
Scope: invoices:read · Requiere el módulo de Facturación en tu plan. Solo lectura: ciclo de cobro completo — enviadas, leídas (apertura del correo/página), pagos parciales, pagadas, vencidas.
Lista facturas. Filtros: status (draft, sent, viewed, partial, paid, overdue, cancelled, void), date_from/date_to (fecha de emisión), search, overdue_only=1. Incluye amount_paid, balance_due, first_viewed_at y public_url.
Detalle completo: ítems, retenciones y payments (cada pago registrado con fecha, monto y método).
Resumen de cartera: by_status, viewed_count, overdue_count, total_invoiced, total_collected, total_outstanding.
🎫 Tickets y Órdenes de Trabajo NUEVO
Scopes: tickets:read / tickets:write · Requiere el módulo de Tickets en tu plan. Soporta plantillas con campos personalizados y órdenes de servicio en campo (dirección + horario + empleados + firma del cliente).
Plantillas activas con su definición de campos (fields: key, label, type, required, options). Consúltalas antes de crear para saber qué datos enviar en custom_fields.
Lista tickets. Filtros: status, service_status (scheduled, en_route, in_progress, completed), priority, assigned_to, date_from/date_to, search.
Detalle: mensajes públicos, eventos, asignados y bloque work_order (estado de servicio, dirección, campos custom, firmado por/cuándo, work_order_url). Las notas internas y la imagen de la firma nunca se exponen.
Totales por estado + resumen de órdenes de trabajo por estado de servicio.
Crea un ticket. Body: {subject, description?, priority?, phone?, contact_name?, contact_email?, template_id?, custom_fields?, service_address?, service_date?, service_time?, service_end_time?, assignee_ids?, notify_customer?}. Con plantilla field_service genera la orden de trabajo, notifica al cliente, envía a cada empleado su enlace de ejecución y programa recordatorios automáticos (1 día antes y 2 horas antes, ajustados al horario hábil configurado por el negocio).
Cambia el estado. Body: {status} (open, in_progress, waiting_customer, resolved, closed) o {service_status} para órdenes (solo la siguiente transición válida; completed requiere la firma del cliente en el portal del empleado).
Responde al cliente (le notifica por WhatsApp/email) o registra nota interna con {private: true}.
Asigna asesores. Body: {assignee_ids: []}. En órdenes de trabajo re-notifica los enlaces de ejecución.
🗓️ Recordatorios recurrentes NUEVO
Scopes: reminders:read / reminders:write. Programa mensajes de WhatsApp que se repiten (semanal, mensual, cada N días…) hacia un cliente — ideal para recordatorios de cobro de cuotas. El envío pasa por la cola anti-baneo y respeta el horario hábil (nunca de madrugada).
Lista tus planes. Filtro status (active, paused, finished) + paginación. Cada uno incluye next_run_at (UTC) y occurrences_sent.
Crea un plan. Body: {title, phone, display_name?, template_id?|body?, variables?, instance_id?, frequency, day_of_week?, day_of_month?, every_n_days?, send_time, timezone, skip_weekends?, starts_on, ends_on?, max_occurrences?}.
Detalle del plan.
Edita el plan (recalcula el próximo envío).
Pausar / reanudar (reanudar recalcula la próxima ocurrencia hacia el futuro).
Historial de ocurrencias con el estado real del envío (queued/sent/failed).
Elimina el plan.
Ejemplo — cobrar la cuota mensual el día 5 a las 9:00 (Bogotá):
curl -X POST "https://panel.smsenlinea.com/api/v1/reminders" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Cuota mensual plan hosting",
"phone": "573001112233",
"display_name": "María Gómez",
"template_id": 12,
"variables": { "monto": "$50.000", "referencia": "CT-2024-11" },
"frequency": "monthly",
"day_of_month": 5,
"send_time": "09:00",
"timezone": "America/Bogota",
"skip_weekends": true,
"starts_on": "2026-08-01"
}'
// Respuesta
{ "ok": true, "data": { "id": 42, "next_run_at": "2026-08-05 14:00:00" } }
La plantilla puede usar {{nombre}} (del contacto) y tus variables ({{monto}}, {{referencia}}…). next_run_at viene en UTC; si la hora local cae fuera del horario hábil, se corre al inicio de la jornada. Para "día 31" en meses cortos se usa el último día del mes.
👤 Cuenta y Plan
Scope: account:read
Información del tenant (nombre, email, fecha de alta).
Plan vigente: nombre, límites (instancias, mensajes/mes, asesores) y features incluidas.
Estado de la suscripción y fecha de renovación/vencimiento.
Consumo del período: mensajes usados vs límite del plan, instancias activas.
Información de la API Key usada (nombre, scopes, último uso).
Límites de tasa vigentes y consumo actual.
📊 Analíticas
Scope: analytics:read. Todas aceptan date_from/date_to.
Panorama general: mensajes, conversaciones, contactos nuevos, ventas.
Mensajes enviados y recibidos por día, con totales del rango.
Conversaciones nuevas/resueltas y tiempos de atención.
Actividad por instancia (útil con varias líneas de WhatsApp).
Desempeño por asesor.
Ventas del período (módulo e-commerce).
Productos más vendidos.
Uso de la propia API (llamadas por endpoint y por día).
📣 Marketing
Plantillas, respuestas rápidas y campañas masivas.
CRUD de plantillas de mensaje (POST/PUT/DELETE en /templates/{id}; categorías en /templates/categories).
Crear plantillas desde la API
Hay dos tipos distintos y no se crean igual:
1 · Plantilla del panel — texto guardado que se envía
como mensaje normal. No pasa por Meta, está disponible al instante y sirve para números por QR
y oficiales. Admite los marcadores {{name}}, {{phone}},
{{company}}, {{advisor}} y {{date}}.
curl -X POST https://panel.smsenlinea.com/api/v1/templates \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "Aviso de recoleccion",
"message_text": "Hola {{name}}, tu recoleccion de {{company}} quedo agendada.",
"category": "Operaciones",
"description": "Aviso al cliente"
}'
// Respuesta: la API detecta sola las variables del texto
{ "ok": true, "data": { "id": 12, "variables": ["name", "company"], ... } }
Se listan con GET /api/v1/templates, se editan con
PUT /api/v1/templates/{id} y se borran con DELETE. Para enviarlas:
POST /api/v1/messages/send-template con template_id.
2 · Plantilla aprobada de WhatsApp (Meta) — la única
que permite escribir fuera de la ventana de 24 h. Se crea en la WABA de una instancia
meta_cloud y la revisa Meta (24-48 h).
curl -X POST https://panel.smsenlinea.com/api/v1/instances/50/templates \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "Aviso de Recoleccion",
"language": "es",
"category": "UTILITY",
"header_type": "TEXT",
"header_text": "Recoleccion programada",
"body": "Hola {{1}}, tu recoleccion quedo agendada para el {{2}}. Te esperamos.",
"body_examples": ["Ana", "01/09/2026"],
"footer": "Gracias por confiar en nosotros",
"buttons": [
{ "type": "QUICK_REPLY", "text": "Confirmar" },
{ "type": "URL", "text": "Ver detalle", "url": "https://tusitio.com/ot/{{1}}" }
]
}'
| Campo | Para qué |
|---|---|
name | Se normaliza a minúsculas con guiones bajos. La respuesta trae el nombre final en name_sent. |
category | UTILITY, MARKETING o AUTHENTICATION. |
body | Variables posicionales {{1}}, {{2}}… No puede terminar en una variable ni ser casi solo variables. |
body_examples | Un valor de ejemplo por cada variable del cuerpo. Meta los exige. |
header_type | NONE, TEXT, IMAGE, VIDEO o DOCUMENT. |
buttons | Array de QUICK_REPLY, URL o COPY_CODE. Los de URL dinámica llevan {{1}} al final. |
footer | Opcional. No admite variables. |
Antes de llamar a Meta se validan las reglas: si algo no cumple responde
422 con la lista exacta de errores, sin gastar el intento. Errores propios:
not_supported (la instancia no es de Meta) y template_not_created
(Meta la rechazó).
Después: GET /api/v1/instances/{id}/templates para ver su estado
(PENDING → APPROVED) y el bloque requires;
POST /api/v1/instances/{id}/templates/sync para refrescar desde Meta; y
POST /api/v1/instances/{id}/templates/ai para que la IA redacte el borrador.
CRUD de respuestas rápidas.
CRUD de campañas + control: start, pause, resume, cancel, recipients, stats.
CRUD de etiquetas para clasificar conversaciones.
🛒 E-commerce
Requiere el módulo e-commerce en tu plan.
CRUD de productos + POST /products/{id}/images + POST /products/{id}/send-to-chat.
CRUD de categorías del catálogo.
Órdenes: listar, crear, cambiar estado/pago, refund, send-payment-link.
Valida un cupón; CRUD completo en /coupons.
📦 Media y 🪝 Webhooks (gestión)
Sube un archivo y devuelve la URL para usar en send-media/send-document. También GET /media/url, GET /media/download, GET /media/quota, DELETE /media.
CRUD de webhooks + POST /webhooks/{id}/test + GET /webhooks/{id}/deliveries + reintento de entregas. Catálogo de eventos en GET /webhooks/events.
Selección de instancia
Tu cuenta puede tener varias instancias de WhatsApp (varias líneas/números). Todos los endpoints de envío te permiten elegir desde cuál sale cada mensaje.
Cómo funciona
| Parámetro del body | Tipo | Comportamiento |
|---|---|---|
instance_id | número | Envía por esa instancia. Debe ser tuya y estar connected. |
instance_name | texto | Alternativa amigable: el nombre exacto de la instancia (como aparece en el panel). Se ignora si también envías instance_id. |
| (ninguno) | — | Se usa la primera instancia conectada de tu cuenta. Cómodo con una sola línea; impredecible con varias — especifícala siempre si tienes más de una. |
Paso 1 — Consulta tus instancias
curl -H "Authorization: Bearer TU_API_KEY" \
"https://panel.smsenlinea.com/api/v1/instances?status=connected"
// Respuesta (resumida)
{
"ok": true,
"data": [
{ "id": 12, "instance_name": "ventas-principal", "status": "connected", "phone_number": "573001112233" },
{ "id": 15, "instance_name": "soporte", "status": "connected", "phone_number": "573004445566" }
]
}
El filtro ?status=connected devuelve solo las instancias listas para enviar.
Paso 2 — Envía indicando la instancia
// Por ID
curl -X POST "https://panel.smsenlinea.com/api/v1/messages/send" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "573009998877",
"message": "Hola, tu pedido está listo 🎉",
"instance_id": 15
}'
// O por nombre (equivalente)
curl -X POST "https://panel.smsenlinea.com/api/v1/messages/send" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "573009998877",
"message": "Hola, tu pedido está listo 🎉",
"instance_name": "soporte"
}'
Aplica a todos los envíos
instance_id / instance_name funcionan igual en: send, send-media, send-audio, send-document, send-location, send-contact, send-template, send-buttons, send-list, send-poll, send-bulk, forward y messages/schedule. También en POST /conversations al iniciar una conversación nueva.
Si la instancia no está disponible
Cuando la instancia pedida no existe, no es tuya o está desconectada, la API responde 400 con el código no_connected_instance — e incluye tus instancias disponibles para que corrijas sin adivinar:
{
"ok": false,
"error": "no_connected_instance",
"message": "La instancia instance_id=99 no existe en tu cuenta o no está conectada.",
"details": {
"available_instances": [
{ "id": 12, "instance_name": "ventas-principal", "status": "connected", "phone_number": "573001112233" },
{ "id": 15, "instance_name": "soporte", "status": "disconnected", "phone_number": "573004445566" }
],
"hint": "Envía instance_id (número) o instance_name (texto) de una instancia con status \"connected\". Si tienes varias instancias, especifícala siempre."
}
}
instance_id en la configuración de tu integración (no lo resuelvas en cada envío) y refresca la lista solo si recibes no_connected_instance. Los IDs son estables; el nombre puede cambiar si lo renombras en el panel.
Campañas masivas
Envíos masivos a una lista de destinatarios, con control de ritmo, ventana horaria y rotación entre números.
Crea la campaña en estado draft. Body: {name, message_text, instance_ids[], recipients[], scheduled_at?, send_window_start?, send_window_end?, min_delay?, max_delay?, batch_size?, batch_interval?, rotation_type?}.
Pone la campaña en cola de envío. Exige al menos una instancia y un destinatario.
Enviados, entregados, leídos, fallidos y respuestas.
También: GET /campaigns, GET|PUT|DELETE /campaigns/{id}, /pause, /resume, /cancel y GET /campaigns/{id}/recipients.
Campañas por número oficial de Meta
En un envío masivo todos los destinatarios están fuera de la ventana de 24 horas, así que WhatsApp solo admite plantillas aprobadas. Si las instancias de la campaña son oficiales, hay que enviar la plantilla; el texto libre se rechaza al crearla en lugar de fallar destinatario por destinatario.
curl -X POST https://panel.smsenlinea.com/api/v1/campaigns \
-H "Authorization: Bearer TU_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "Promo agosto",
"instance_ids": [50],
"meta_template_name": "recordatorio_cuota",
"meta_template_lang": "es",
"meta_template_vars": ["{{name}}", "Compuservices", "Asesor 1"],
"message_text": "(se rellena con el cuerpo de la plantilla)",
"recipients": [{"phone":"573001112233","name":"Ana"}]
}'
Cada valor de meta_template_vars ocupa un hueco de la plantilla ({{1}}, {{2}}…) y admite los mismos tokens que el texto libre: {{name}}, {{phone}} o las variables de cada destinatario.
Errores propios de campañas
| Código | Qué significa |
|---|---|
mixed_providers | Mezclaste números oficiales y por QR en la misma campaña: admiten contenido distinto |
template_required | Instancia oficial sin meta_template_name / meta_template_lang |
template_not_approved | La plantilla no existe para esa instancia o Meta aún no la aprobó |
template_vars_missing | Faltan valores en meta_template_vars para los huecos de la plantilla |
Bajas de marketing (obligatorio conocerlo)
Cuando un contacto escribe BAJA, STOP, «no más mensajes» o similar, el sistema lo registra, le confirma automáticamente y lo excluye de todas las campañas de ese negocio, sea cual sea el número por el que se envíe. Vuelve a entrar escribiendo ALTA.
El filtro se aplica al cargar los destinatarios, también por API: si tu lista trae 500 números y 12 están dados de baja, la campaña se crea con 488. No hay forma de saltárselo, y es lo que protege la calidad del número frente a los reportes de los usuarios.
Webhooks
Los webhooks permiten que tu aplicación reciba notificaciones en tiempo real cuando ocurren eventos importantes.
Los acuses de entrega y lectura funcionan igual con cualquier número. La presencia del cliente (en línea / escribiendo) solo la entrega WhatsApp en las líneas conectadas por QR; si migras a WhatsApp oficial (Meta) dejarás de recibir contact.presence, porque Meta no publica ese dato. Tenlo en cuenta si tu integración depende de él.
Eventos Disponibles
| Evento | Descripción |
|---|---|
message.received |
Se recibió un mensaje nuevo |
message.sent |
Se envió un mensaje |
message.delivered |
El mensaje llegó al teléfono del cliente (✓✓ gris) |
message.read |
El cliente leyó el mensaje (✓✓ azul) |
message.failed |
WhatsApp no pudo entregar el mensaje |
message.updated |
Cualquier cambio de estado del mensaje. Equivale a los tres anteriores juntos: úsalo si prefieres un solo evento y filtrar por el campo status |
contact.presence |
El cliente está en línea o escribiendo. Campos: state (composing, recording, available, unavailable, paused) e is_typing. Solo llega en líneas conectadas por QR: WhatsApp oficial (Meta) no informa la presencia de los clientes |
flow.response |
Un cliente rellenó y envió un formulario de WhatsApp. Campos: flow_token (el que mandaste al enviarlo, para saber a qué envío corresponde), answers con las respuestas, conversation_id, from y message_id. Solo en números oficiales de Meta |
chat.assigned |
Una conversación fue asignada a un agente |
chat.created |
Se creó una nueva conversación |
contact.updated |
La información de un contacto fue actualizada |
instance.connected |
Una instancia de WhatsApp se conectó |
Verificar Webhooks
Cada webhook incluye una firma HMAC-SHA256 en el encabezado X-Webhook-Signature. Siempre verifica la firma para garantizar que el webhook proviene de smsenlinea.com:
Python
import hmac
import hashlib
def verify_webhook(request_body, signature, secret):
expected = hmac.new(
secret.encode(),
request_body.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
PHP
<?php
function verifyWebhook($payload, $signature, $secret) {
// Calcular la firma esperada
$expected = hash_hmac(
'sha256',
$payload,
$secret,
false // retornar como string hexadecimal
);
// Comparar de forma segura
return hash_equals($expected, $signature);
}
// Ejemplo de uso en un endpoint de webhook
$secret = 'wh_1a2b3c4d5e6f7g8h'; // Tu secret token del webhook
// Obtener el cuerpo de la solicitud
$payload = file_get_contents('php://input');
// Obtener la firma del encabezado
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
// Verificar la firma
if (!verifyWebhook($payload, $signature, $secret)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// La firma es válida, procesar el webhook
$data = json_decode($payload, true);
if ($data['event'] === 'message.received') {
// Procesar mensaje recibido
$phone = $data['data']['phone'];
$message = $data['data']['message'];
// Tu lógica aquí
echo "Mensaje recibido de {$phone}: {$message}";
}
http_response_code(200);
echo json_encode(['success' => true]);
?>
Configurar un Webhook
Dirígete a Integraciones → Webhooks y crea un nuevo webhook con tu URL.
Rate Limiting
La API de smsenlinea.com aplica límites de velocidad basados en tu plan.
Límites por Plan
| Plan | Req/Minuto | Req/Hora | Req/Día |
|---|---|---|---|
| Free | 100 | 1,000 | 10,000 |
| Starter | 500 | 10,000 | 100,000 |
| Professional | 2,000 | 50,000 | 1,000,000 |
| Enterprise | Ilimitado | Ilimitado | Ilimitado |
Encabezados de Rate Limit
Cada respuesta incluye encabezados que indican tu uso:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1653225600
Manejar Límites Excedidos
Cuando excedes el límite, recibirás una respuesta 429 Too Many Requests:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"ok": false,
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again in 60 seconds."
}
Manejo de Errores
La API usa códigos de estado HTTP estándar para indicar el resultado de las solicitudes.
Códigos de Estado
| Código | Significado |
|---|---|
| 200 | Solicitud exitosa |
| 201 | Recurso creado |
| 400 | Solicitud inválida (parámetros incorrectos) |
| 401 | No autorizado (clave API inválida) |
| 403 | Prohibido (permisos insuficientes) |
| 404 | No encontrado |
| 429 | Demasiadas solicitudes (rate limit) |
| 500 | Error del servidor |
Respuesta de Error
Los errores incluyen más información útil:
{
"ok": false,
"error": "invalid_phone",
"message": "El número de teléfono proporcionado no es válido",
"code": "INVALID_INPUT"
}
Ejemplos de Código
JavaScript/Node.js
const fetch = require('node-fetch');
async function sendMessage() {
const response = await fetch('https://panel.smsenlinea.com/api/v1/external/messages/send', {
method: 'POST',
headers: {
'X-API-Key': 'whasaas_live_...',
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: '573001234567',
message: 'Hola desde smsenlinea.com'
})
});
const data = await response.json();
console.log(data);
}
sendMessage();
Python
import requests
def send_message():
response = requests.post(
'https://panel.smsenlinea.com/api/v1/external/messages/send',
headers={
'X-API-Key': 'whasaas_live_...',
'Content-Type': 'application/json'
},
json={
'phone': '573001234567',
'message': 'Hola desde smsenlinea.com'
}
)
print(response.json())
send_message()
PHP
<?php
function sendMessage() {
$apiKey = 'whasaas_live_...';
$url = 'https://panel.smsenlinea.com/api/v1/external/messages/send';
$data = [
'phone' => '573001234567',
'message' => 'Hola desde smsenlinea.com'
];
$options = [
'http' => [
'header' => [
'X-API-Key: ' . $apiKey,
'Content-Type: application/json'
],
'method' => 'POST',
'content' => json_encode($data),
'timeout' => 10
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
$result = json_decode($response, true);
print_r($result);
}
sendMessage();
// Alternativa usando cURL (recomendado):
function sendMessageWithCurl() {
$apiKey = 'whasaas_live_...';
$url = 'https://panel.smsenlinea.com/api/v1/external/messages/send';
$data = [
'phone' => '573001234567',
'message' => 'Hola desde smsenlinea.com'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . $apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
echo "HTTP Status: " . $httpCode . "\n";
print_r($result);
}
sendMessageWithCurl();
?>
cURL
curl -X POST https://panel.smsenlinea.com/api/v1/external/messages/send \
-H "X-API-Key: whasaas_live_..." \
-H "Content-Type: application/json" \
-d '{
"phone": "573001234567",
"message": "Hola desde smsenlinea.com"
}'
Mejores Prácticas
🔐 Seguridad
- Nunca compartas tus claves API públicamente
- Usa diferentes claves para dev, staging y production
- Rota tus claves cada 90 días
- Usa IP whitelist para claves de producción
- Siempre verifica las firmas de webhooks
⚡ Rendimiento
- Usa batch operations cuando sea posible
- Implementa caché para reducir llamadas API
- Pagina los resultados grandes (limit, offset)
- Usa webhooks en lugar de polling
- Implementa reintentos exponenciales para resiliencia
📊 Monitoring
- Log todas las llamadas API importantes
- Monitora la tasa de errores 5xx
- Alerta cuando se acerque al límite de rate limit
- Rastrear latencia de webhooks
- Mantén métricas de tasa de entrega de webhooks