Empieza gratis
NuevoLímite de peticiones con Retry-AfterVer

De tu primer envío a un webhook firmado.

Una API REST para WhatsApp, SMS, RCS, email y push, con claves de prueba y de producción, reintentos seguros y eventos firmados. Todo el contrato público está en openapi.json.

Las claves de prueba empiezan por ccx_test_ y usan una cartera de prueba. No llega nada a ningún cliente.

Base de la APIhttps://api.messages.ccxcompany.co
Inicio rápidoccx_test_
Listo. No llegó nada a ningún cliente y no se cobró nada.
  1. Crea una clave de APIccx_test_••••••••Se muestra una vez
  2. Envía el mensajePOST /messages201 en cola
  3. Recibe el estado en tu webhookmessage.deliveredFirma válida
Ejemplo con una clave de prueba. Los valores con … son de ejemplo.

Tu primer envío, en tres pasos.

Empieza con una clave de prueba: usa la cartera de prueba y un envío simulado, así que nada llega a ningún cliente y no se cobra nada de verdad.

POST /messages
curl -X POST https://api.messages.ccxcompany.co/messages \  -H "Authorization: Bearer $CCX_API_KEY" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: pedido-4821-enviado" \  -d '{    "numberId": "num_…",    "to": "+34…",    "type": "text",    "payload": { "text": { "body": "Tu pedido ha salido para entrega" } }  }'

Cuerpo resumido; el objeto completo del mensaje está en openapi.json. Los valores con … son de ejemplo.

Claves, ámbitos y reintentos seguros.

Cada llamada lleva Authorization: Bearer con tu clave. Cada ruta exige un ámbito, y una clave solo puede hacer lo que permiten sus ámbitos.

ccx_test_Clave de pruebaUsa la cartera de prueba y el transporte simulado. Sirve para validar la integración sin gastar.
ccx_live_Clave de producciónEnvía de verdad y descuenta de la cartera de producción en el momento del envío. Es la clave guardada en nuestro lado la que decide el entorno, no el cuerpo de la petición.
Ámbitosmessages:sendmessages:readsms:sendemail:sendAlgunos ejemplos. Una clave sin el ámbito de la ruta recibe un 403. Crea claves en Configuración, API Keys; el secreto se muestra una sola vez.
metadataUn objeto tuyo que viaja con el mensaje y vuelve intacto en cada webhook. Útil para guardar el id del pedido en tu sistema.

Reintenta sin enviar dos veces.

En los envíos de WhatsApp, SMS y push, la cabecera Idempotency-Key te deja repetir la misma llamada durante 24 horas. Misma clave y mismo cuerpo devuelven la respuesta original. Misma clave con otro cuerpo devuelve 409. Pruébalo.

Usa un valor propio, como el número de pedido, para que un reintento después de un timeout no envíe dos veces.
La clave vale 24 horas. Después, el mismo valor cuenta como un envío nuevo.
El 409 llega con meta.code IDEMPOTENCY_CONFLICT. Usa una clave nueva para un envío nuevo. En RCS, cualquier clave repetida devuelve 409.
  1. Idempotency-Key: pedido-4821 · cuerpo A201msg_01… creado y en cola
  2. Idempotency-Key: pedido-4821 · cuerpo A201Respuesta original de msg_01…, no se envió nada otra vez.
  3. Idempotency-Key: pedido-4821 · cuerpo B409CONFLICT · IDEMPOTENCY_CONFLICT
1mensaje creado

Respuestas simuladas y resumidas. Los valores con … son de ejemplo.

Webhooks firmados, con reentrega.

Registra la URL, elige los eventos y guarda el secreto. Cada entrega lleva X-CCX-Signature, X-CCX-Event, X-CCX-Delivery-Id y X-CCX-Idempotency-Key.

Responde 2xx en menos de 10 segundosPon el trabajo en cola y devuelve 200 al momento. Más de 10 segundos cuenta como fallo.
Cinco intentos másCon 5xx, 408, 429 o sin respuesta: tras 5 s, 30 s, 2 min, 10 min y 1 h. Cualquier otro 4xx es un rechazo definitivo, y 410 desactiva el webhook.
Gestiona la reentregaLa reentrega es normal: usa X-CCX-Idempotency-Key para registrar cada evento una sola vez.
Valida antes de guardarPOST /webhooks/validate envía un ping firmado a la URL antes de registrarla. Rota el secreto con POST /webhooks/{id}/rotate-secret.
Reintentos cuando tu endpoint responde 5xx, 408 o 429, o no responde
  1. 5 s
  2. 30 s
  3. 2 min
  4. 10 min
  5. 1 h
import { createHmac, timingSafeEqual } from "node:crypto"; // X-CCX-Signature: t=<unix>,v1=<hex>export function firmaValida(secreto, cuerpo, cab) {  const p = Object.fromEntries(    cab.split(",").map((kv) => kv.split("=")));  if (Math.abs(Date.now() / 1000 - p.t) > 300) return false;  const esperado = createHmac("sha256", secreto)    .update(p.t + "." + cuerpo).digest("hex");  return esperado.length === p.v1.length &&    timingSafeEqual(Buffer.from(esperado, "hex"),                    Buffer.from(p.v1, "hex"));}

Calcúlala sobre el cuerpo en bruto, tal como llegó, sin volver a serializar el JSON. La ventana de 5 minutos en la marca t bloquea la repetición de eventos antiguos.

  • message.sent
  • message.delivered
  • message.read
  • message.failed
  • message.received
  • template.approved
  • template.rejected
  • sms.delivered
  • sms.received
  • email.delivered
  • email.bounced
  • email.opened
  • wallet.low_balance

Cuando la respuesta no es 2xx.

Todo error llega como problem details (RFC 7807), con un code estable que tu código puede comparar. Elige uno para ver su respuesta.

Respuesta · 409 CONFLICT
HTTP/1.1 409Content-Type: application/problem+json {  "type": "about:blank",  "title": "conflict",  "code": "CONFLICT",  "status": 409,  "detail": "idempotency key reused with a different payload",  "meta": { "code": "IDEMPOTENCY_CONFLICT" }}

Ejemplo de cuerpo de error, tal como lo devuelve la API.

Lo que puedes hacer con la API.

Las rutas de abajo forman parte del contrato público. Lo que no está en openapi.json no es contrato y puede cambiar.

WhatsAppPOST /messagesGET /messagesGET /messages/{id}
SMSPOST /v2/sms/messagesPOST /v2/sms/previewGET /v2/sms/replies
RCSPOST /v2/rcs/messagesGET /v2/rcs/messages/{id}
EmailPOST /v2/email/messagesGET /v2/email/messages/{id}
PushPOST /v2/push/messagesPOST /v2/push/tokensPOST /v2/push/preview
Contactos y listas/contacts/contacts/import/v2/lists/v2/segments
Plantillas y flowsGET /v2/templatesPOST /v2/templatesGET /v2/flows
Webhooks/webhooksPOST /webhooks/validatePOST /webhooks/{id}/rotate-secret
Recorridos/v2/journeysPOST /v2/journeys/{id}/activatePOST /v2/journeys/{id}/entries
Exportaciones y carteraGET /v2/exports/{whatsapp|sms|rcs|email}GET /wallet

Guías para lo que más se atasca.

Respuestas cortas a las dudas que aparecen cuando la integración sale de la pizarra.

¿Cuántos segmentos me costará un SMS?
71CaracteresGSM-7Codificación1Segmento

Acentos cambiados antes del envío: á pasa a a. Todo el mensaje cabe en GSM-7, así que sigue valiendo 160 caracteres por segmento.

Contado como lo cuenta la factura. POST /v2/sms/preview devuelve los segmentos y el precio antes de enviar. El SMS va a números de Brasil; las rutas internacionales son bajo consulta.

Meta rechazó mi plantilla. ¿Y ahora?

Casi siempre es formato, no contenido: una promoción declarada como utilidad, una variable al principio o al final del cuerpo, o un botón de URL sin un prefijo fijo. Antes de enviarla a Meta, el editor del panel avisa cuando el texto usa lenguaje promocional agresivo, cuando una plantilla de marketing no tiene botón de baja y cuando el cuerpo supera los 1024 caracteres.

¿Cuánto tarda la aprobación del número?

Son dos pasos. La verificación del número, con el código por SMS o llamada, es rápida. La verificación del negocio, que desbloquea límites de envío más altos, la hace la propia Meta, y el plazo es suyo.

¿Cuántos segmentos me costará un SMS?

Hasta 160 caracteres es un segmento; a partir de ahí, uno cada 152. Con emoji, el mensaje va en Unicode: hasta 70 es un segmento, a partir de ahí uno cada 67. Los caracteres acentuados que no existen en GSM-7 se sustituyen por su equivalente sin tilde cuando todo el mensaje cabe en GSM-7, así que los acentos no encarecen. POST /v2/sms/preview devuelve los segmentos y el precio antes de enviar. El SMS va a números de Brasil; las rutas internacionales son bajo consulta.

¿Qué pasa si se acaba el saldo?

La cartera es prepago y se descuenta en el momento del envío. Sin saldo, SMS, RCS y email responden 402, y los envíos de WhatsApp y push fallan con el estado de saldo insuficiente. Una campaña de SMS que se queda sin saldo a mitad de envío se pausa con el motivo en pantalla, y Reanudar envía los mensajes rechazados por falta de saldo junto con el resto. La cartera de prueba es independiente y nunca cobra dinero real.

Mis emails llegan a spam

Publica SPF, DKIM y DMARC del dominio, sube el volumen poco a poco en un dominio nuevo, quita las direcciones no válidas y separa el transaccional del marketing en subdominios. Las direcciones que rebotan pasan a la lista de supresión.

Mi webhook no recibe los eventos

Comprueba que la URL responde 2xx en menos de 10 segundos, que la firma se calcula sobre el cuerpo en bruto y que el webhook está activo, no pausado. Un 4xx de tu lado (salvo 408 y 429) no se reintenta; los 5xx y la falta de respuesta sí, tras 5 s, 30 s, 2 min, 10 min y 1 h.

¿Cómo invito a mi equipo y defino permisos?

Invita por email y elige uno de los nueve roles, de propietario a solo lectura. La organización puede exigir verificación en dos pasos a todos los miembros, con 30 días de margen. No hay cuota por usuario, así que nadie tiene que compartir el acceso.

¿Atascado en una ruta? Habla con quienes mantienen la API.

Si creas la cuenta desde la web, recibes 5 US$ de crédito de prueba.