Conexla

API pública v1

Documentación de la API de Conexla

Toda la documentación en español, con ejemplos de curl reales y copiables. Crea tus claves en Ajustes → API. Base URL: https://tuapp.conexla.tech.

Autenticación

Cada petición lleva tu clave en la cabecera Authorization, con el esquema Bearer. La clave tiene el formato cnx_live_<id>.<secreto> — la parte antes del punto identifica la clave (se muestra siempre en tu panel), la parte de después del punto es el secreto y solo se enseña UNA vez al crearla. Guárdala como si fuera una contraseña: no hay forma de volver a verla, solo de revocarla y crear una nueva.

curl https://tuapp.conexla.tech/api/v1/contactos \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Ámbitos (scopes)

Cada clave tiene uno o varios ámbitos. Una petición a una operación sin el ámbito necesario responde 403 (FORBIDDEN_SCOPE).

ÁmbitoPermite
mensajes:enviarPOST /api/v1/mensajes
contactos:leerGET /api/v1/contactos
contactos:escribirPOST /api/v1/contactos
conversaciones:leerGET /api/v1/conversaciones

Gestionar tus propios webhooks (/api/v1/webhooks) no exige ningún ámbito de negocio concreto: cualquier clave activa puede administrar sus URLs.

Límites de uso

Cada clave tiene un límite de peticiones por minuto (ventana deslizante: se cuentan las peticiones de los últimos 60 segundos en cada momento, no un contador que resetea en punto). Toda respuesta incluye estas cabeceras:

CabeceraSignificado
X-RateLimit-LimitPeticiones permitidas por minuto para esta clave.
X-RateLimit-RemainingCuántas te quedan en la ventana actual.
X-RateLimit-ResetInstante (epoch, segundos) en que vuelve a haber cupo.
Retry-AfterSolo en 429: segundos que conviene esperar antes de reintentar.

Superar el límite responde 429 con código RATE_LIMITED.

Enviar un mensaje

POST /api/v1/mensajes — requiere el ámbito mensajes:enviar. Identifica al destinatario por telefono (se crea el contacto si no existía) o por contactoId.

Regla de la ventana de 24 horas (igual que en la bandeja): si la última respuesta libre de tu negocio fue hace más de 24 horas desde el último mensaje entrante de ese contacto, solo puedes reabrir la conversación con tipo: "plantilla". Intentar tipo: "texto" fuera de ventana responde 422 WINDOW_CLOSED — es una regla de Meta, no una limitación nuestra: fuera de ventana, WhatsApp solo entrega plantillas aprobadas.

# Texto libre (dentro de la ventana de 24h)
curl -X POST https://tuapp.conexla.tech/api/v1/mensajes \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "telefono": "+34600111222",
    "tipo": "texto",
    "texto": "¡Hola! Tu pedido ya está listo para recoger."
  }'
# Plantilla (funciona también FUERA de la ventana de 24h)
curl -X POST https://tuapp.conexla.tech/api/v1/mensajes \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "telefono": "+34600111222",
    "tipo": "plantilla",
    "plantilla": { "nombre": "recordatorio_cita", "idioma": "es", "variables": ["Marta", "18:00"] }
  }'

Respuesta 201: { "ok": true, "datos": { "mensaje": {...}, "conversacionId": "...", "contactoId": "..." } }

Contactos

GET /api/v1/contactos — ámbito contactos:leer. Paginación por cursor: pasa el cursorSiguiente de una respuesta como ?cursor= en la siguiente petición hasta que venga null.

curl "https://tuapp.conexla.tech/api/v1/contactos?limite=20" \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Página siguiente:
curl "https://tuapp.conexla.tech/api/v1/contactos?limite=20&cursor=MjAyNi0wOC0xN1QxMDowMDowMFp8ZGVtby1jb250YWN0by0y" \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

POST /api/v1/contactos — ámbito contactos:escribir. Deduplica por teléfono normalizado: si ya existe, actualiza sus etiquetas en vez de duplicarlo.

curl -X POST https://tuapp.conexla.tech/api/v1/contactos \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "telefono": "+34600111222", "nombre": "Marta Gómez", "etiquetas": ["cliente_habitual"] }'

Conversaciones

GET /api/v1/conversaciones — ámbito conversaciones:leer. Admite estado (abierta/pendiente/cerrada), busqueda y paginación por cursor.

curl "https://tuapp.conexla.tech/api/v1/conversaciones?estado=abierta&limite=10" \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Webhooks salientes

Registra una URL para recibir eventos en tiempo real (no necesitas hacer polling). Al registrarla, mandamos un reto de verificación firmado y exigimos que lo devuelvas — así comprobamos que controlas de verdad esa URL antes de confiarle nada. Solo aceptamos https:// públicas (nunca IPs privadas ni localhost).

curl -X POST https://tuapp.conexla.tech/api/v1/webhooks \
  -H "Authorization: Bearer cnx_live_ab12cd34.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://miapp.com/webhooks/conexla", "eventos": ["mensaje_entrante"] }'

Tu endpoint debe responder 200 con { "reto": "<el mismo valor recibido>" } durante la verificación. La respuesta de éxito trae el secreto de firma — se muestra UNA vez, guárdalo.

Verificar la firma de cada evento

Cada evento llega firmado en la cabecera X-Conexla-Signature con el formato sha256=<hex>: HMAC-SHA256 del cuerpo exacto (bytes crudos, antes de parsear JSON) con tu secreto.

// Node.js — verificación en tiempo constante
import { createHmac, timingSafeEqual } from "node:crypto";

function firmaValida(cuerpoCrudo, firmaRecibida, secreto) {
  const esperada = "sha256=" + createHmac("sha256", secreto).update(cuerpoCrudo).digest("hex");
  const a = Buffer.from(firmaRecibida);
  const b = Buffer.from(esperada);
  return a.length === b.length && timingSafeEqual(a, b);
}

GET /api/v1/webhooks lista tus webhooks (sin el secreto). DELETE /api/v1/webhooks?id=<id> elimina uno.

Catálogo de errores

Toda respuesta de error tiene la forma { "ok": false, "error": { "codigo", "mensaje", "idPeticion" } }. codigo es estable (compara tu integración contra él, en inglés); mensaje es siempre español legible, listo para enseñárselo a tu cliente final sin traducir. idPeticion identifica la petición en nuestros logs si necesitas soporte.

CódigoHTTPMensaje (español)
UNAUTHORIZED401La clave de API no es válida, está revocada o no se ha proporcionado. Revisa la cabecera 'Authorization: Bearer <clave>'.
FORBIDDEN_SCOPE403Esta clave de API no tiene permiso para esta operación. Revisa sus ámbitos (scopes) en Ajustes → API.
RATE_LIMITED429Has superado el límite de peticiones por minuto de esta clave. Reduce la frecuencia o espera al tiempo indicado en 'Retry-After'.
VALIDATION_ERROR400Los datos enviados no son válidos. Revisa 'detalles' para saber qué campo falla.
NOT_FOUND404No se encontró el recurso solicitado.
WINDOW_CLOSED422La ventana de servicio de 24 horas está cerrada para esta conversación: solo se puede reabrir con una plantilla aprobada por Meta, no con texto libre.
CONFLICT409El recurso ya existe o entra en conflicto con el estado actual.
ACCOUNT_NOT_CONNECTED409Este tenant todavía no tiene ninguna cuenta de WhatsApp conectada. Conéctala desde /conectar antes de usar la API de mensajes.
WEBHOOK_VERIFICATION_FAILED422No se pudo verificar que controlas esa URL: el reto de verificación no se respondió correctamente o a tiempo.
URL_NOT_ALLOWED400Esa URL no está permitida por motivos de seguridad (solo HTTPS público, no direcciones internas o privadas).
META_DELIVERY_FAILED502Meta rechazó el envío del mensaje. Revisa 'detalles' para el motivo exacto (número inválido, plantilla no aprobada, cuenta con restricciones...).
INTERNAL_ERROR500Ha ocurrido un error interno. Si persiste, contacta con soporte indicando el identificador de esta petición (idPeticion).