ChatFlowy Orchestration API v1
API REST server-to-server para integrar sistemas externos (ERP, cobranza, CRM) con la plataforma de mensajería masiva WhatsApp de ChatFlowy. Permite crear plantillas, gestionar audiencias, lanzar campañas y recibir notificaciones en tiempo real mediante webhooks.
https://us-central1-{project}.cloudfunctions.net/orchestrationApi/orchestration/v1application/jsonGoogle Cloud us-central1Ejemplo de URL completa
BASE_URL = https://us-central1-{project}.cloudfunctions.net/orchestrationApi
PREFIX = /orchestration/v1
Ejemplo completo de endpoint:
https://us-central1-{project}.cloudfunctions.net/orchestrationApi/orchestration/v1/templatesResumen de endpoints
| Método | Path | Descripción | Scope |
|---|---|---|---|
| POST | /templates | Crear plantilla | templates:write |
| GET | /templates | Listar plantillas | templates:read |
| GET | /templates/:id | Obtener plantilla | templates:read |
| POST | /audiences | Crear audiencia | audiences:write |
| GET | /audiences/:id | Obtener audiencia | audiences:read |
| POST | /campaigns | Crear campaña | campaigns:write |
| GET | /campaigns | Listar campañas | campaigns:read |
| GET | /campaigns/:id | Obtener campaña | campaigns:read |
| POST | /campaigns/:id/cancel | Cancelar campaña | campaigns:write |
| POST | /whitelist | Upsert whitelist | whitelist:write |
| POST | /webhooks/subscriptions | Crear suscripción | webhooks:write |
| GET | /webhooks/subscriptions | Listar suscripciones | webhooks:read |
| GET | /webhooks/subscriptions/:id | Obtener suscripción | webhooks:read |
| DELETE | /webhooks/subscriptions/:id | Eliminar suscripción | webhooks:write |
| GET | /operations/:id | Estado de operación | operations:read |
Autenticación y Headers
Todos los endpoints requieren autenticación Bearer + headers de tenant. Las credenciales se obtienen en el panel de ChatFlowy → Integraciones → API Clients.
Headers requeridos en cada request
| Header | Tipo | Descripción |
|---|---|---|
| Authorization | string | Bearer token. Formato: Bearer {token} |
| X-ChatFlowy-Company-Id | string | ID de la empresa. Debe coincidir con el companyId del token. |
| X-ChatFlowy-Account-Id | string | ID de la cuenta WhatsApp Business. Debe pertenecer al companyId. |
| Content-Type | string | Requerido en POST: application/json |
| idempotency-key | string | UUID único por operación. Ver sección Idempotencia. |
Authorization: Bearer eyJhbGciOi...
X-ChatFlowy-Company-Id: comp_abc123
X-ChatFlowy-Account-Id: acc_xyz789
Content-Type: application/jsonScopes disponibles
Cada cliente de integración tiene asignados uno o más scopes. El scope * otorga acceso total.
| Scope | Descripción |
|---|---|
| templates:read | Listar y obtener plantillas |
| templates:write | Crear plantillas y enviarlas a Masiv |
| campaigns:read | Listar y obtener campañas |
| campaigns:write | Crear y cancelar campañas |
| audiences:read | Obtener audiencias y su estado de validación |
| audiences:write | Crear audiencias (todos los modos) |
| whitelist:write | Insertar y actualizar contactos en whitelist |
| webhooks:read | Listar y obtener suscripciones de webhook |
| webhooks:write | Crear y eliminar suscripciones de webhook |
| operations:read | Consultar estado de operaciones asíncronas |
| * | Acceso total a todos los scopes |
companyId del token no coincide con el header X-ChatFlowy-Company-Id, el servidor responde 403 TENANT_MISMATCH. Los headers de tenant siempre deben corresponder a las credenciales del Bearer token. Manejo de Errores
Todos los errores retornan JSON con la estructura {error: {code, message, detail?} }. El campo detail es opcional y provee contexto adicional.
Estructura de respuesta de error
{
"error": {
"code": "AUDIENCE_NOT_READY",
"message": "Audience is not yet validated (status != ready)",
"detail": "Optional additional context"
}
}Catálogo de códigos de error
| Código | HTTP | Descripción |
|---|---|---|
| INVALID_REQUEST | 400 | Campo faltante, tipo incorrecto o valor fuera de rango. |
| UNAUTHORIZED | 401 | Bearer token ausente, inválido o inactivo. |
| FORBIDDEN | 403 | Acceso denegado al recurso solicitado. |
| NOT_FOUND | 404 | El recurso no existe o no pertenece a este tenant. |
| CONFLICT | 409 | Nombre duplicado, estado inválido para la operación, etc. |
| RATE_LIMITED | 429 | Demasiadas solicitudes en un periodo corto. |
| INTERNAL_ERROR | 500 | Error inesperado del servidor. Reportar con timestamp. |
| TENANT_MISMATCH | 403 | El companyId del token no coincide con X-ChatFlowy-Company-Id. |
| SCOPE_DENIED | 403 | El token no tiene el scope requerido para este endpoint. |
| IDEMPOTENCY_CONFLICT | 409 | Misma Idempotency-Key enviada con payload diferente. |
| AUDIENCE_NOT_READY | 409 | La audiencia no ha terminado de validarse (status ≠ READY). |
| TEMPLATE_NOT_APPROVED | 409 | La plantilla no está aprobada (status ≠ APPROVED). |
| OPERATION_LOCKED | 409 | La operación ya está siendo procesada por otro worker. |
| MASIV_ERROR | 502 | El proveedor Masiv rechazó la solicitud. Solo aplica a POST /templates. |
| MASIV_MISSING_ID | 502 | Masiv respondió OK pero no devolvió un templateId válido. |
Idempotencia
El header idempotency-key previene operaciones duplicadas en caso de timeouts, reintentos de red o errores transitorios. Recomendado para todos los endpoints POST.
idempotency-keyComportamiento
409 IDEMPOTENCY_CONFLICT.POST /orchestration/v1/campaigns
idempotency-key: 550e8400-e29b-41d4-a716-446655440000
// Primera llamada → 202 + campaña creada
// Misma key, mismo payload → 202 + respuesta cacheada (no se crea otra campaña)
// Misma key, payload diferente → 409 IDEMPOTENCY_CONFLICTPlantillas
Las plantillas son mensajes predefinidos y aprobados por Meta/WhatsApp. Una plantilla debe tener estado APPROVED antes de poder usarse en campañas. La aprobación la gestiona el proveedor Masiv y típicamente toma 24–48 horas hábiles.
/orchestration/v1/templatesCrea una nueva plantilla y la envía a Masiv para su revisión y aprobación por Meta/WhatsApp. Solo se guarda en Firestore si Masiv retorna un ID válido.
Body — Campos requeridos
| Campo | Tipo | Descripción |
|---|---|---|
| hsmName | string | Nombre técnico único de la plantilla. Se normaliza automáticamente: minúsculas, sin acentos, espacios→guiones bajos. Máx. 512 chars. |
| hsmText | string | Cuerpo del mensaje. Variables con doble llave: {{NOMBRE}}. No puede empezar ni terminar con una variable. |
| language | enum | "Español" | "Inglés" |
| category | enum | "Marketing" | "Utility" |
Body — Campos opcionales
| Campo | Tipo | Descripción |
|---|---|---|
| header | string | Encabezado visible del mensaje. Sin emojis ni markdown. |
| footer | string | Pie de página del mensaje. |
| variables | string[] | Lista de nombres de variables. Si se omite, se auto-extrae del hsmText. |
| buttons | object | Botones interactivos. Ver tabla de botones abajo. |
Ejemplo — Con Quick Reply
POST /orchestration/v1/templates
Authorization: Bearer {token}
X-ChatFlowy-Company-Id: {companyId}
X-ChatFlowy-Account-Id: {accountId}
Content-Type: application/json
{
"hsmName": "pago_vencido_v1",
"hsmText": "Hola {{NOMBRE}}, tu pago de ${{MONTO}} está vencido desde {{FECHA}}. Regularízalo para evitar cargos adicionales.",
"language": "Español",
"category": "Marketing",
"header": "Aviso de Pago",
"footer": "Autopartes Orión • No responder a este mensaje",
"buttons": {
"type": "QUICK_REPLY",
"items": [
{ "type": "QUICK_REPLY", "text": "Ya pagué" },
{ "type": "QUICK_REPLY", "text": "Necesito más tiempo" }
]
}
}Ejemplo — Con Call To Action
// Ejemplo con botones CALL_TO_ACTION
{
"hsmName": "bienvenida_con_portal_v1",
"hsmText": "Hola {{NOMBRE}}, bienvenido a Autopartes Orión. Accede a tu portal de cliente.",
"language": "Español",
"category": "Utility",
"buttons": {
"type": "CALL_TO_ACTION",
"items": [
{ "type": "URL", "text": "Abrir portal", "url": "https://portal.empresa.com" },
{ "type": "PHONE_NUMBER", "text": "Llamar soporte", "phoneNumber": "5512345678" }
]
}
}Respuesta — 201 Created
HTTP 201 Created
{
"id": "tpl_9kX2mN8pQr",
"hsmName": "pago_vencido_v1",
"hsmTemplateId": "16641",
"status": "PENDING",
"message": "Template submitted to Masiv and saved."
}/orchestration/v1/templatesRetorna todas las plantillas de la empresa ordenadas por fecha de creación descendente.
Respuesta — Objeto ExternalTemplate
| Campo | Tipo | Descripción |
|---|---|---|
| id | string | ID único Firestore |
| hsmName | string | Nombre técnico normalizado |
| hsmText | string | Cuerpo del mensaje con variables |
| status | enum | PENDING | SUBMITTED | APPROVED | REJECTED | DISABLED |
| variables | string[] | Nombres de variables extraídos del texto |
| category | string | "Marketing" | "Utility" |
| language | string | "Español" | "Inglés" |
| header | string | Encabezado del mensaje |
| footer | string | Pie de página del mensaje |
| providerId | string | null | ID numérico de Masiv. Null mientras PENDING. |
| account | object | {id, name} — cuenta WhatsApp Business |
| createdAt | string | ISO 8601 |
| updatedAt | string | null | ISO 8601 |
Respuesta — 200 OK
HTTP 200 OK
{
"data": [
{
"id": "tpl_9kX2mN8pQr",
"hsmName": "pago_vencido_v1",
"hsmText": "Hola {{NOMBRE}}, tu pago de ${{MONTO}} está vencido...",
"status": "APPROVED",
"variables": ["NOMBRE", "MONTO", "FECHA"],
"category": "Marketing",
"language": "Español",
"header": "Aviso de Pago",
"footer": "Autopartes Orión • No responder",
"isMedia": false,
"providerId": "16641",
"companyId": "comp_abc123",
"account": { "id": "acc_xyz789", "name": "WhatsApp Principal" },
"createdAt": "2026-02-15T18:30:00.000Z",
"updatedAt": "2026-02-16T09:15:00.000Z"
}
],
"total": 1
}/orchestration/v1/templates/{templateId}Retorna una plantilla específica. Útil para polling del estado de aprobación.
Audiencias
Una audiencia es la lista de destinatarios para una campaña. Soporta 3 modos de creación. Tras la creación, el sistema valida automáticamente los teléfonos, deduplica y verifica variables de plantilla. La audiencia debe alcanzar el estado READY antes de usarse en una campaña.
/orchestration/v1/audiencesCrea una nueva audiencia o vincula una existente. El campo mode determina qué campos adicionales se requieren.
Campos por modo
| Campo | Tipo | Modo | Descripción |
|---|---|---|---|
| mode | enum | Todos | INLINE_NUMBERS | FILE_BASE64 | EXISTING_AUDIENCE |
| name | string | INLINE, FILE | Nombre único de la audiencia en la empresa. |
| existingAudienceId | string | EXISTING | ID de la audiencia ya validada a vincular. |
| rows | object[] | INLINE | Array de filas. Cada fila necesita un campo de teléfono + variables de plantilla. |
| fileBase64 | string | FILE | Contenido del archivo CSV/XLSX en Base64. Acepta data URI: data:text/csv;base64,... |
| fileName | string | FILE | Nombre del archivo con extensión. Ayuda a detectar XLSX vs CSV. |
| templateId | string | INLINE, FILE | Si se provee, valida que la plantilla sea APPROVED y que las columnas de sus variables existan en el archivo. |
telefono, phone, numero, celular, whatsapp (sin importar mayúsculas ni acentos).Normalización de encabezados: todos los headers se convierten a MAYÚSCULAS, sin acentos, espacios→guiones_bajos. Ej: "Número de Teléfono" → "NUMERO_DE_TELEFONO".
Ejemplo — INLINE_NUMBERS
// Modo INLINE_NUMBERS
POST /orchestration/v1/audiences
Content-Type: application/json
{
"mode": "INLINE_NUMBERS",
"name": "Cartera Vencida Marzo 2026",
"templateId": "tpl_9kX2mN8pQr",
"rows": [
{ "telefono": "5512345678", "NOMBRE": "Juan Pérez", "MONTO": "1500.00", "FECHA": "2026-02-01" },
{ "telefono": "5598765432", "NOMBRE": "Ana García", "MONTO": "2300.50", "FECHA": "2026-01-28" },
{ "telefono": "5556789012", "NOMBRE": "Luis Torres", "MONTO": "875.00", "FECHA": "2026-02-10" }
]
}Ejemplo — FILE_BASE64
// Modo FILE_BASE64
{
"mode": "FILE_BASE64",
"name": "Cartera Vencida Marzo 2026",
"fileName": "cartera_marzo.csv",
"templateId": "tpl_9kX2mN8pQr",
"fileBase64": "dGVsZWZvbm8sTk9NQlJFLE1PVlRPCjU1MTIzNDU2NzgsSk..."
}
// También acepta XLSX:
{
"mode": "FILE_BASE64",
"name": "Cartera Vencida Marzo 2026",
"fileName": "cartera_marzo.xlsx",
"fileBase64": "UEsDBBQABgAIAAAAIQD..."
}Ejemplo — EXISTING_AUDIENCE
// Modo EXISTING_AUDIENCE — vincular audiencia ya validada
{
"mode": "EXISTING_AUDIENCE",
"existingAudienceId": "aud_7vBnKp3mRx"
}Respuesta — 202 Accepted (INLINE / FILE)
HTTP 202 Accepted
{
"id": "aud_7vBnKp3mRx",
"status": "UPLOADED",
"message": "Audience uploaded. Validation will begin automatically."
}Respuesta — 200 OK (EXISTING)
HTTP 200 OK (modo EXISTING_AUDIENCE)
{
"id": "aud_7vBnKp3mRx",
"name": "Cartera Vencida Febrero 2026",
"status": "READY",
"templateId": "tpl_9kX2mN8pQr",
"stats": {
"totalRows": 3,
"validRows": 3,
"invalidRows": 0,
"uniquePhones": 3,
"droppedDuplicates": 0
},
"companyId": "comp_abc123",
"createdAt": "2026-02-18T10:00:00.000Z",
"linked": true
}Normalización de teléfonos mexicanos
Entrada → Resultado interno
──────────────────────────────────────────────────────
"5512345678" → 525512345678 (10 dígitos, agrega 52)
"525512345678" → 525512345678 (ya correcto)
"5215512345678" → 525512345678 (521 + 10 dígitos, quita el 1)
"+52 55 1234-5678" → 525512345678 (separadores ignorados)
"0052 55 1234 5678" → 525512345678 (prefijo 00 eliminado)
"1234" → INVÁLIDO (no cumple /^52\d{10}$/)linked: true./orchestration/v1/audiences/{audienceId}Retorna el estado actual de una audiencia incluyendo las estadísticas de validación. Usar para polling hasta que status === "READY".
Respuesta — 200 OK
HTTP 200 OK
{
"id": "aud_7vBnKp3mRx",
"name": "Cartera Vencida Marzo 2026",
"status": "READY",
"templateId": "tpl_9kX2mN8pQr",
"stats": {
"totalRows": 150,
"validRows": 147,
"invalidRows": 3,
"uniquePhones": 147,
"droppedDuplicates": 0
},
"companyId": "comp_abc123",
"createdAt": "2026-02-20T14:30:00.000Z",
"updatedAt": "2026-02-20T14:32:15.000Z"
}totalRows — Filas procesadas por el validadorvalidRows — Filas con teléfono válido + variables completasinvalidRows — Filas descartadas por teléfono inválido o variables faltantesuniquePhones — Teléfonos distintos tras deduplicacióndroppedDuplicates — Filas eliminadas por teléfono duplicadoCampañas
Una campaña envía una plantilla aprobada a todos los destinatarios de una audiencia. Puede ejecutarse de forma inmediata o programarse para una fecha futura. La plantilla debe ser APPROVED y la audiencia debe ser READY.
/orchestration/v1/campaignsCrea y lanza una campaña. Si se provee scheduledAt, la campaña queda en estado SCHEDULED y se ejecuta automáticamente en la fecha indicada (precisión ±5 min).
Body
| Campo | Tipo | Descripción |
|---|---|---|
| name | string | Nombre descriptivo de la campaña. |
| templateId | string | ID de plantilla APPROVED. |
| audienceId | string | ID de audiencia READY. |
| scheduledAt | string | Fecha/hora de ejecución programada. Ver tabla de formatos. |
Formatos de scheduledAt aceptados
| Ejemplo | Interpretación |
|---|---|
| "2026-03-15 22:00" | 10:00 PM México City (UTC-6) |
| "2026-03-15 10:00pm" | 10:00 PM México City |
| "15/03/2026 22:00" | DD/MM/YYYY, México City |
| "2026-03-15" | Medianoche México City |
| "2026-03-15T22:00:00.000Z" | ISO 8601 UTC explícito |
| "2026-03-15T16:00:00-06:00" | ISO 8601 con offset explícito |
scheduledAt debe ser al menos 10 minutos en el futuro. Fechas sin timezone se asumen como México City (UTC-6).Ejemplo — Campaña inmediata
// Campaña inmediata
POST /orchestration/v1/campaigns
Content-Type: application/json
{
"name": "Recordatorio Pago Marzo 2026",
"templateId": "tpl_9kX2mN8pQr",
"audienceId": "aud_7vBnKp3mRx"
}Ejemplo — Campaña programada
// Campaña programada — múltiples formatos de fecha aceptados
{
"name": "Promoción Semana Santa",
"templateId": "tpl_9kX2mN8pQr",
"audienceId": "aud_7vBnKp3mRx",
"scheduledAt": "2026-03-15 10:00"
}
// Otros formatos válidos (todos asumen México City si no hay timezone):
// "2026-03-15 10:00pm" → 10:00 PM CDMX
// "15/03/2026 10:00" → DD/MM/YYYY HH:MM CDMX
// "2026-03-15" → Medianoche CDMX
// "2026-03-15T10:00:00.000Z" → UTC explícito
// "2026-03-15T10:00:00-06:00" → Offset explícito
// Mínimo: 10 minutos en el futuroRespuesta — 202 Accepted (inmediata)
HTTP 202 Accepted
{
"id": "cmp_4dLnRk9wTy",
"name": "Recordatorio Pago Marzo 2026",
"status": "RUNNING",
"templateId": "tpl_9kX2mN8pQr",
"audienceId": "aud_7vBnKp3mRx",
"companyId": "comp_abc123",
"account": { "id": "acc_xyz789", "name": "WhatsApp Principal" },
"sent": 0,
"rejected": 0,
"failed": 0,
"createdAt": "2026-02-21T22:00:00.000Z",
"message": "Campaign created and queued for execution."
}Respuesta — 202 Accepted (programada)
HTTP 202 Accepted
{
"id": "cmp_8xMpQv2nFg",
"status": "SCHEDULED",
"scheduledAt": "2026-03-15T16:00:00.000Z",
"message": "Campaign scheduled successfully."
}/orchestration/v1/campaignsLista las campañas de la empresa, ordenadas por fecha de creación descendente.
Query params
| Param | Tipo | Default | Descripción |
|---|---|---|---|
| limit | number | 50 | Máximo de campañas a retornar. Máximo absoluto: 200. |
Respuesta — 200 OK
HTTP 200 OK
{
"data": [
{
"id": "cmp_4dLnRk9wTy",
"name": "Recordatorio Pago Marzo 2026",
"status": "COMPLETED",
"templateId": "tpl_9kX2mN8pQr",
"audienceId": "aud_7vBnKp3mRx",
"account": { "id": "acc_xyz789", "name": "WhatsApp Principal" },
"sent": 145,
"rejected": 2,
"failed": 0,
"createdAt": "2026-02-21T22:00:00.000Z",
"updatedAt": "2026-02-21T22:04:30.000Z"
}
],
"total": 1
}/orchestration/v1/campaigns/{campaignId}Obtiene el estado actual de una campaña específica. Usar para polling de sent, rejected y failed.
/orchestration/v1/campaigns/{campaignId}/cancelCancela una campaña. No se puede cancelar una campaña en estado COMPLETED o COMPLETED_WITH_ERRORS.
// POST /orchestration/v1/campaigns/{campaignId}/cancel
HTTP 200 OK
{
"id": "cmp_8xMpQv2nFg",
"status": "CANCELLED"
}Whitelist
Inserta o actualiza contactos en la whitelist. Los contactos de la whitelist son los únicos que pueden recibir mensajes de la cuenta. La operación es un upsert idempotente — repetir la misma fila no crea duplicados (el DocId es determinístico: phone_companyId).
/orchestration/v1/whitelistUpsert de hasta 20,000 contactos por llamada. Los teléfonos se normalizan automáticamente.
Estructura de cada fila en rows[]
| Campo | Alias aceptados | Req. | Descripción |
|---|---|---|---|
| phone | telefono, numero, numeroTelefono | Sí | Teléfono del contacto. |
| nombre | name, nombreDeudor, deudor | No | Nombre del contacto. |
| identificador | identifier, id_cliente, idCliente | No | ID del cliente en tu sistema. |
| labels | label, etiquetas, tags | No | Etiquetas. Array o string separado por "/". Se convierten a CamelCase. Ej: ["ClienteVIP"] |
| url | link, enlace | No | URL de perfil o portal del cliente. |
Ejemplo
POST /orchestration/v1/whitelist
Content-Type: application/json
{
"rows": [
{
"phone": "5512345678",
"nombre": "Juan Pérez",
"identificador": "CLI-00123",
"labels": ["ClienteVIP", "CarteraActiva"],
"url": "https://portal.empresa.com/clientes/00123"
},
{
"telefono": "+52 55 9876-5432",
"nombre": "Ana García",
"identificador": "CLI-00456"
}
]
}Respuesta — 200 OK
HTTP 200 OK
{
"upsertedCount": 2,
"skippedCount": 0,
"skippedReturned": 0,
"skipped": []
}
// Ejemplo con un teléfono inválido:
{
"upsertedCount": 1,
"skippedCount": 1,
"skippedReturned": 1,
"skipped": [
{
"reason": "Invalid phone. Expected 52 + 10 digits (received: "123")",
"row": { "phone": "123", "nombre": "Dato inválido" }
}
]
}Webhooks
Los webhooks eliminan la necesidad de polling. ChatFlowy envía un HTTP POST a tu endpoint registrado cuando ocurren eventos (cambio de estado de plantilla, audiencia o campaña). Los intentos fallidos se reintenten con backoff exponencial.
/orchestration/v1/webhooks/subscriptionsCrea una suscripción de webhook. El secret solo se retorna en este momento — guárdalo de forma segura.
Body
| Campo | Tipo | Descripción |
|---|---|---|
| url | string | URL HTTPS de tu endpoint que recibirá los eventos. |
| events | string[] | Lista de eventos. Usa ["*"] para todos los eventos. |
secret de la respuesta solo se muestra una vez. Guárdalo en un gestor de secretos seguro (AWS Secrets Manager, GCP Secret Manager, etc.). No se puede recuperar después.Ejemplo
POST /orchestration/v1/webhooks/subscriptions
Content-Type: application/json
{
"url": "https://mi-servidor.com/hooks/chatflowy",
"events": [
"template.status.changed",
"campaign.validation.ready",
"campaign.execution.completed",
"campaign.execution.completed_with_errors"
]
}
// Para recibir todos los eventos:
{ "url": "https://mi-servidor.com/hooks/chatflowy", "events": ["*"] }Respuesta — 201 Created
HTTP 201 Created
{
"id": "whs_3fRnKm9pBx",
"url": "https://mi-servidor.com/hooks/chatflowy",
"events": ["template.status.changed", "campaign.validation.ready", "campaign.execution.completed"],
"status": "ACTIVE",
"secret": "a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
"message": "Subscription created. Store the secret securely – it will not be shown again."
}/orchestration/v1/webhooks/subscriptionsLista las suscripciones activas del tenant. El secret no se incluye en la respuesta.
/orchestration/v1/webhooks/subscriptions/{subscriptionId}Obtiene una suscripción específica. Retorna {id, url, events, status, createdAt}.
/orchestration/v1/webhooks/subscriptions/{subscriptionId}Desactiva la suscripción (status → INACTIVE). Retorna {id, status: INACTIVE}.
Catálogo de eventos
| Evento | Disparador | Payload (resumen) |
|---|---|---|
template.status.changed | El estado de una plantilla cambia (cualquier transición) | templateId, statusBefore, statusAfter, template.{id, hsmName, status, companyId, accountId} |
campaign.validation.ready | Audiencia validada exitosamente (status → READY) | audienceId, statusBefore, statusAfter, audience.{id, name, status, stats, companyId, accountId} |
campaign.validation.failed | Validación de audiencia fallida (status → INVALID | ERROR) | audienceId, statusBefore, statusAfter, audience.{id, name, status, stats, ...} |
campaign.validation.changed | Cualquier cambio de estado en audiencia | audienceId, statusBefore, statusAfter, audience{...} |
campaign.execution.started | Campaña comienza ejecución (status → RUNNING) | campaignId, statusBefore, statusAfter, campaign.{id, name, status, sent, rejected, failed, ...} |
campaign.execution.completed | Campaña completada (status → COMPLETED) | campaignId, statusBefore, statusAfter, campaign{...} |
campaign.execution.completed_with_errors | Completada con errores (status → COMPLETED_WITH_ERRORS) | campaignId, statusBefore, statusAfter, campaign{...} |
campaign.execution.failed | Campaña falló (status → ERROR) | campaignId, statusBefore, statusAfter, campaign{...} |
campaign.execution.changed | Cualquier cambio de estado en campaña | campaignId, statusBefore, statusAfter, campaign{...} |
* | Wildcard — recibe todos los eventos anteriores | Depende del evento |
Ejemplo de payload recibido
// Evento: campaign.execution.completed
// POST a tu endpoint:
{
"event": "campaign.execution.completed",
"campaignId": "cmp_4dLnRk9wTy",
"statusBefore": "RUNNING",
"statusAfter": "COMPLETED",
"campaign": {
"id": "cmp_4dLnRk9wTy",
"name": "Recordatorio Pago Marzo 2026",
"status": "COMPLETED",
"companyId": "comp_abc123",
"accountId": "acc_xyz789",
"sent": 145,
"rejected": 2,
"failed": 0
}
}Verificación de firma
Cada evento incluye el header X-ChatFlowy-Signature: t={t},v1={sig} donde sig = HMAC-SHA256(secret, t + "." + rawBody). Verifica siempre la firma antes de procesar el evento.
// Header recibido en tu endpoint:
X-ChatFlowy-Signature: t=1708555200,v1=a3f8b2c1d4e...
// Verificación (Node.js):
const [tPart, vPart] = signature.split(',');
const t = tPart.split('=')[1];
const v1 = vPart.split('=')[1];
const rawBody = JSON.stringify(body);
const expected = crypto
.createHmac('sha256', YOUR_SECRET)
.update(t + '.' + rawBody)
.digest('hex');
const isValid = crypto.timingSafeEqual(
Buffer.from(v1),
Buffer.from(expected)
);Operaciones Asíncronas
Las operaciones asíncronas rastrean el estado de trabajos de larga duración. Disponible para consulta de estado.
/orchestration/v1/operations/{operationId}Retorna el estado de una operación asíncrona.
HTTP 200 OK
{
"id": "op_5kRmQn7vXz",
"type": "TEMPLATE_SUBMIT",
"status": "COMPLETED",
"payload": { "templateId": "tpl_9kX2mN8pQr" },
"result": { "hsmTemplateId": "16641" },
"attempts": 1,
"createdAt": "2026-02-15T18:30:00.000Z",
"updatedAt": "2026-02-15T18:30:05.000Z",
"completedAt": "2026-02-15T18:30:05.000Z"
}Catálogo de Estados
Plantillas
Campañas
Audiencias
Operaciones
Flujos de Integración
Guías paso a paso para los escenarios más comunes de integración.
Flujo 1 — Primera campaña (plantilla + audiencia nuevas)
Flujo completo desde cero para enviar una campaña por primera vez.
/templatesCrear la plantilla. El nombre se normaliza automáticamente (minúsculas, sin acentos). Retorna status: PENDING.
template.status.changedEsperar hasta que statusAfter === APPROVED (24–48h hábiles). Alternativa: poll GET /templates/:id.
/audiencesSubir audiencia (INLINE_NUMBERS o FILE_BASE64). Retorna status: UPLOADED.
campaign.validation.readyEsperar statusAfter === READY. Alternativa: poll GET /audiences/:id cada 5s.
/campaignsCrear campaña con templateId + audienceId. La ejecución comienza de inmediato.
campaign.execution.completedRecibir resultado con sent/rejected/failed. Alternativa: poll GET /campaigns/:id.
Flujo 2 — Campaña con audiencia existente
Reusar una audiencia ya validada para una nueva campaña.
/audiences (EXISTING_AUDIENCE)Pasar existingAudienceId. Retorna estado actual + linked: true. La audiencia debe tener status READY.
/campaignsUsar el mismo audienceId. No se crea un documento de audiencia nuevo.
Flujo 3 — Campaña programada
Programar una campaña para ejecutarse automáticamente en una fecha futura.
/campaigns (con scheduledAt)scheduledAt mínimo 10 min en el futuro. Fechas sin timezone = México City (UTC-6). Status inicial: SCHEDULED.
Worker automático (±5 min)El sistema ejecuta la campaña automáticamente. Precisión: ±5 minutos.
/campaigns/:id/cancel (opcional)Cancelar antes de la fecha programada si es necesario.
Flujo 4 — Setup de Webhooks (recomendado para producción)
Eliminar el polling y recibir notificaciones automáticas en tiempo real.
/webhooks/subscriptionsRegistrar endpoint con los eventos necesarios (ej: ["template.status.changed","campaign.validation.ready","campaign.execution.completed"]). Guardar el secret de forma segura — solo se muestra una vez.
Verificar firma en tu servidorExtraer t y v1 del header X-ChatFlowy-Signature. Calcular HMAC-SHA256(secret, t+"."+rawBody) y comparar con v1.
Operar sin pollingLos eventos llegarán automáticamente. Reintentos con backoff exponencial si tu servidor no responde 2xx.
Glosario
Definición de términos usados en la API y la documentación.
Preguntas Frecuentes
Contacta al equipo de ChatFlowy para soporte técnico de integraciones.