Autenticación sin vueltas
Creas una clave en Ajustes → API y la envías como Authorization: Bearer. Se muestra una sola vez y puedes revocarla cuando quieras sin afectar las otras integraciones.
La API REST y los webhooks de WhatsApp de Omniia conectan tu sistema con tus conversaciones: envías plantillas y mensajes, creas o actualizas contactos y recibes cada evento firmado con HMAC-SHA256. Esta página es la referencia práctica para desarrolladores, con ejemplos en curl y Node.js que coinciden con lo que hoy responde la API.

Omniia / Bandeja de entrada
Conversaciones
María López🤖 ACTIVA
WhatsApp Meta API • 573001234567
Ventana de conversación abierta · tiempo restante 23:12
Hoy
10:24 a. m.
Bot
¡Hola María! 👋 Claro, te muestro la disponibilidad. Elige una fecha:
10:25 a. m.
Escribe un mensaje…
Creas una clave en Ajustes → API y la envías como Authorization: Bearer. Se muestra una sola vez y puedes revocarla cuando quieras sin afectar las otras integraciones.
Omniia compara el nombre, el idioma, la cantidad de variables y el archivo del encabezado con tu copia local de plantillas. Si algo no cuadra, responde con un error claro antes de gastar la llamada a Meta.
Cada webhook trae X-Omniia-Signature y X-Omniia-Timestamp. Con el secreto de la suscripción compruebas que viene de Omniia y descartas reenvíos viejos.
Si tu servidor no responde 2xx, Omniia reintenta a 1 minuto, 5 minutos, 30 minutos y 2 horas. En Ajustes → API ves cada entrega y la puedes reintentar a mano.
Actualizado el 15 de octubre de 2026
Son tres piezas. La API REST recibe lo que tu sistema le pide a Omniia: enviar, crear, consultar, cerrar. Los webhooks le avisan a tu sistema lo que pasa en Omniia. Y la URL de una automatización arranca una secuencia sin clave de API. Para qué sirve cada una en un negocio está en integrar WhatsApp con tu CRM o sistema; aquí vamos al detalle técnico.
La base es https://crm.omniia.app/api/v1 y todo va en JSON. Los errores traen un campo error con el código y, a veces, un detail. Los teléfonos van solo con dígitos e indicativo (573001234567), las fechas en ISO 8601 y UTC, y los ids son UUID que entrega la misma API. La documentación completa está dentro de la app, en Ajustes → API, y se descarga en PDF.
| Ruta | Para qué sirve |
|---|---|
| GET /me | Probar la clave |
| GET /channels | Canales con su id, tipo (oficial o QR) y estado |
| GET /agents | Asesores, para asignar el chat al enviar |
| GET /templates | Plantillas con categoría, variables y tipo de encabezado |
| POST /messages | Enviar texto, archivo o plantilla |
| GET /messages | Lo que entró o salió desde una fecha |
| GET y POST /contacts, GET /contacts/:id | Buscar, crear y leer contactos |
| PATCH /contacts/:id y /contacts/by-phone/:phone | Actualizar datos y campos personalizados |
| GET /contact-fields | Lista de campos personalizados |
| GET /contacts/:id/documents | Archivos del contacto |
| GET /conversations y /conversations/:id/messages | Chats y su historial |
| POST /conversations/:id/close y /reopen | Cerrar o reabrir |
Solo el dueño o un administrador crea claves, en Ajustes → API. La clave empieza por omn_live_ y se muestra una sola vez: Omniia guarda solo su huella. Usa una por integración (“n8n producción”, “ERP”, “sitio web”): si una se filtra, la revocas y las demás siguen andando. Va en cada petición con Authorization: Bearer; una clave inválida responde 401 con invalid_api_key.
curl https://crm.omniia.app/api/v1/me \
-H "Authorization: Bearer omn_live_TU_CLAVE"
# -> { "workspace": { "id": "<uuid>", "name": "Mi empresa", "slug": "mi-empresa" } }
curl https://crm.omniia.app/api/v1/channels \
-H "Authorization: Bearer omn_live_TU_CLAVE"
# -> [{ "id": "<uuid>", "name": "Ventas", "type": "meta", "status": "connected", "enabled": true }]Para escribirle primero a un cliente por un canal de la API oficial necesitas una plantilla aprobada por Meta. El orden es este:
curl -X POST https://crm.omniia.app/api/v1/messages \
-H "Authorization: Bearer omn_live_TU_CLAVE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: FE-20931" \
-d '{
"channelId": "<id-del-canal-oficial>",
"to": "573001234567",
"type": "template",
"template": {
"name": "factura_emitida",
"language": "es_CO",
"variables": ["Laura", "FE-20931", "$184.900"],
"headerMedia": {
"type": "document",
"mediaUrl": "https://tu-servidor.com/facturas/FE-20931.pdf",
"filename": "Factura-FE-20931.pdf"
}
},
"assignedUserId": "<id-del-asesor>"
}'
# -> {
# "messageId": "<uuid>",
# "conversationId": "<uuid>",
# "contactId": "<uuid>",
# "status": "sent",
# "externalId": "wamid.HBgM...",
# "assignedUserId": "<id-del-asesor>"
# }Omniia crea el contacto si no existe y reutiliza la conversación de ese canal, reabriéndola si estaba cerrada. Con assignedUserId el chat queda fijado a ese asesor y la respuesta del cliente le llega a él. Ojo con el estado: sent quiere decir que Meta aceptó el envío, no que llegó; la entrega, la lectura o el fallo llegan después con el webhook message.status. Si el documento del encabezado es siempre el mismo, no hace falta repetirlo: Omniia usa el que quedó ligado a la plantilla.
Por un canal oficial, un texto libre solo sale si el cliente escribió en las últimas 24 horas; si no, la API responde 403 con meta_window_expired (la regla completa, en la ventana de 24 horas de WhatsApp). Por QR no hay plantillas (template_not_supported_by_channel) y el texto sale en cualquier momento, con los riesgos que explicamos en WhatsApp por QR vs. API oficial.
Si tu sistema repite un POST /messages porque se cortó la red, el cliente no debería recibir dos facturas. Envía Idempotency-Key con un identificador tuyo, único por envío: el número de factura, de radicado o de pedido. Si la clave llega otra vez, Omniia no envía nada y devuelve la respuesta original con Idempotent-Replay: true; si la primera sigue en curso, responde 409. Si el envío falló, la clave se libera. Se recuerdan siete días.
| Respuesta | Qué pasó | Qué hacer |
|---|---|---|
| 401 invalid_api_key | Clave inexistente, revocada o mal escrita | Revisa el encabezado Authorization |
| 403 meta_window_expired | Texto libre fuera de la ventana de 24 horas | Envía una plantilla |
| 404 template_not_found | Ese nombre e idioma no existen en el canal | Compara con GET /templates (refresh=1) |
| 400 template_not_approved | Meta no la ha aprobado o la rechazó | Revisa su estado con status=all |
| 400 template_variables_mismatch | Otra cantidad de variables | Mira expected y received |
| 400 template_header_media_required | Falta el archivo del encabezado | Envía template.headerMedia |
| 429 rate_limited | Superaste el límite por minuto de la clave | Espera lo que diga Retry-After |
| 502 send_failed | El canal rechazó el envío | Lee el campo detail |
El límite se cuenta por clave, con un presupuesto para consultas y otro para escrituras, y cada respuesta trae X-RateLimit-Remaining para frenar antes de chocar. Si consultas en ciclos, usa GET /messages con after y el nextAfter de la respuesta anterior: una llamada trae todo lo nuevo.
Un webhook es un aviso que Omniia le envía a tu servidor cuando algo pasa. En Ajustes → API registras una URL, eliges los eventos y, si quieres, un solo canal. Cada evento llega como un POST JSON con event, timestamp, workspaceId y data.
| Evento | Cuándo llega |
|---|---|
| message.received | Un cliente escribió (con el botón de plantilla que tocó, si lo hizo) |
| message.sent | Salió un mensaje desde la bandeja o la API |
| message.status | Un envío por la API oficial pasó a entregado, leído o fallido |
| conversation.created | Nació una conversación, entrante o por API |
| conversation.assigned | El chat se asignó a un asesor |
| conversation.closed | Se cerró por cualquier vía, con el motivo |
| contact.created | Se creó un contacto en el CRM o por la API |
| csat.responded | El cliente calificó la atención del 1 al 5 |
Un detalle que confunde al principio: cuando una persona nueva escribe por WhatsApp, el evento que te sirve es conversation.created con source inbound. contact.created cubre los contactos que se crean en el CRM o por la API.
POST /webhooks/omniia HTTP/1.1
Content-Type: application/json
X-Omniia-Event: message.received
X-Omniia-Delivery: 6f1c2a9e-1b7d-4c3e-9a51-0d2b7c8e4f10
X-Omniia-Timestamp: 1792073052318
X-Omniia-Signature: sha256=<hmac-en-hexadecimal>
{
"event": "message.received",
"timestamp": 1792073052318,
"workspaceId": "<uuid>",
"data": {
"messageId": "<uuid>",
"conversationId": "<uuid>",
"channelId": "<uuid>",
"channelName": "Ventas",
"from": "573001234567",
"to": "573209876543",
"name": "Laura Gómez",
"type": "text",
"body": "¿La factura FE-20931 ya incluye el IVA?",
"caption": null,
"mediaUrl": null,
"timestamp": "2026-10-15T14:04:11.000Z",
"externalMessageId": "wamid.HBgM...",
"button": null,
"replyTo": null
}
}La firma es un HMAC-SHA256 calculado con el secreto de la suscripción (empieza por whsec_ y lo ves en Ajustes → API) sobre la marca de tiempo, un punto y el cuerpo exacto que llegó. Por eso hay que leer el cuerpo crudo antes de convertirlo en JSON: si lo conviertes y lo vuelves a serializar, la firma deja de cuadrar.
const crypto = require('crypto');
const express = require('express');
const app = express();
// Cuerpo crudo: la firma se calcula sobre el texto exacto que envió Omniia.
app.post('/webhooks/omniia', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Omniia-Timestamp') || '';
const firma = req.get('X-Omniia-Signature') || '';
const cuerpo = req.body.toString('utf8');
const esperada = 'sha256=' + crypto
.createHmac('sha256', process.env.OMNIIA_WEBHOOK_SECRET) // whsec_...
.update(timestamp + '.' + cuerpo)
.digest('hex');
const a = Buffer.from(firma);
const b = Buffer.from(esperada);
const firmaValida = a.length === b.length && crypto.timingSafeEqual(a, b);
const reciente = Math.abs(Date.now() - Number(timestamp)) < 5 * 60 * 1000;
if (!firmaValida || !reciente) return res.sendStatus(401);
res.sendStatus(200); // responde ya; el trabajo pesado va después
const evento = JSON.parse(cuerpo);
procesarEvento(evento, req.get('X-Omniia-Delivery')); // el id de entrega evita procesar dos veces
});
app.listen(3000);La marca de tiempo va en milisegundos y se renueva en cada intento, así que una ventana de cinco minutos no rechaza los reintentos legítimos y sí los reenvíos viejos.
No todo necesita la API. Una automatización con el disparador Otro sistema lo pide tiene su propia URL secreta, de la forma https://crm.omniia.app/api/automations/hook/ seguida de un token. Tu formulario, tu tienda o n8n hacen un POST con el teléfono, y Omniia crea o actualiza el contacto y corre los pasos: esperar, enviar una plantilla o un texto, poner o quitar etiquetas, mover de etapa, asignar, crear una tarea o avisar a otro sistema.
curl -X POST https://crm.omniia.app/api/automations/hook/<token> \
-H "Content-Type: application/json" \
-d '{
"phone": "573001234567",
"name": "Laura Gómez",
"email": "laura@correo.com",
"fields": { "ciudad": "Bogotá" },
"vars": { "factura": "FE-20931", "valor": "$184.900" }
}'
# -> { "ok": true, "enrolled": true, "contactId": "<uuid>" }Solo phone es obligatorio. Lo de fields se guarda en el contacto y lo de vars se usa en los mensajes como {{vars.factura}}. Si la automatización está apagada, responde 409. Activa “Puede volver a entrar” si el mismo cliente debe pasar varias veces, una por factura. Y el paso “Avisar a otro sistema” envía un JSON sin firma: protege esa URL con un token largo.
1
bandeja para todos tus canales
24/7
tu agente de IA responde
1 link
para que tus clientes agenden solos
0
cláusulas de permanencia
¿Tienes otra duda? Escríbenos por WhatsApp y te ayudamos.
Dentro de la app, en Ajustes → API, con todos los campos, ejemplos y errores; también se descarga en PDF. No hay una documentación pública en la web: la ve quien tiene cuenta, y puedes abrirla durante la semana de prueba gratis.
Sí. La misma clave y las mismas rutas sirven para canales por QR y por la API oficial. La diferencia es que por QR no hay plantillas, y por la API oficial un texto libre solo sale dentro de la ventana de 24 horas.
La respuesta de POST /messages con status sent solo indica que Meta aceptó el envío. Suscribe el evento message.status: te avisa cuando el mensaje pasa a entregado, leído o fallido, con el motivo del fallo.
La API responde 429 con el encabezado Retry-After, que dice cuántos segundos esperar. Los límites se cuentan por clave y por minuto, con un presupuesto para consultas y otro para escrituras. Si tu caso necesita más cuota, se pide por soporte.
La API está pensada para envíos uno a uno que dispara tu sistema: una factura, un aviso, un código. Para escribirle a miles de contactos a la vez usa las campañas de Omniia con plantillas aprobadas, que controlan el ritmo y la ventana horaria.
Sí, con una plantilla de categoría autenticación. Se envía una sola variable, el código, y Omniia lo repite en el botón de copiar que exige Meta.
Integrar WhatsApp con tu CRM o sistema: API, webhooks y n8n
Cómo integrar WhatsApp con tu CRM, ERP o sitio web usando la API REST y los webhooks de Omniia o n8n, con casos de uso y un paso a paso.
API de WhatsApp Business
Conecta la API de WhatsApp Business (Meta Cloud API) con Omniia: plantillas, campañas masivas, webhooks, automatizaciones y multiagente. Empieza gratis.
WhatsApp en n8n, Zapier y Make
Conecta WhatsApp con n8n, Zapier o Make usando los webhooks y la API de Omniia: cuándo conviene cada herramienta y cinco recetas paso a paso.
Plantillas de WhatsApp (HSM): categorías y cómo aprobarlas
Qué son las plantillas de WhatsApp (HSM), las categorías marketing, utilidad y autenticación, cuánto cuesta cada una y por qué Meta las rechaza.
Ventana de 24 horas de WhatsApp: qué enviar y cuánto cuesta
Cuándo se abre y se cierra la ventana de 24 horas de la API de WhatsApp, qué enviar dentro y fuera, la de 72 h de los anuncios y el cobro desde octubre.
WhatsApp con HubSpot
Integra WhatsApp con HubSpot con los webhooks y la API de Omniia y n8n, Zapier o Make: leads nuevos, una nota por chat y avisos por etapa del negocio.
Creas tu cuenta en minutos, conectas tu WhatsApp y nuestro equipo te acompaña para dejarlo funcionando.