Broadcasts
Los broadcasts son campañas masivas que envían un template aprobado de
WhatsApp a muchos destinatarios. Son solo para WhatsApp y basados en
templates (un canal de Instagram devuelve 422 channel_not_whatsapp).
Todas las rutas /platform/v1/broadcasts/* requieren send:broadcasts.
El scope es accesible desde una API key (server-to-server) o desde una sesión
cookie del dashboard.
Caps por plan
Section titled “Caps por plan”Dos límites gobiernan los broadcasts (columnas en platform_plans):
| Plan | Destinatarios por campaña | Campañas por período | Efecto |
|---|---|---|---|
free | 0 | 0 | Deshabilitado: POST /broadcasts devuelve 403 broadcasts_not_in_plan. |
lite | 1000 | 10 | Hasta 1000 destinatarios por campaña, 10 campañas por período. |
pro | 10 000 | 100 | Hasta 10 000 destinatarios por campaña, 100 campañas por período. |
business | ∞ | ∞ | Sin límite. |
- El cap de campañas por período (
broadcasts_per_month) cuenta las campañas creadas dentro del período de facturación actual (aniversario de Polar o mes calendario). Pasarte devuelve 429broadcasts_per_month_quotaal crear. - El cap de destinatarios por campaña (
broadcast_recipients_limit) se aplica al cargar destinatarios: los que sobran van al arrayerrors[]conrecipients_limit_reached(no es un 422 duro).
Ciclo de vida (FSM)
Section titled “Ciclo de vida (FSM)”crear ──► [draft] ──schedule──► [scheduled] ──(vence / send)──► [sending] ──► [completed] (≥1 enviado) │ │ │ │ └── cancel ──► [draft] (limpia scheduled_at) └─(0 enviados)─► [failed] └── send ─────────────────────────────────────────────────────────────┘cancelesscheduled → draft(reanudable, limpiascheduled_at). NO hay un estado terminalcancelled: cancelás un agendado y vuelve a borrador, listo para editar o reenviar.- Un broadcast
sendingo terminal NO se puede cancelar (409status_locked). - Un segundo
POST /{id}/sendes idempotente a nivel FSM: una vez ensending, devuelve 409status_locked.
Endpoints
Section titled “Endpoints”| Método + ruta | Descripción |
|---|---|
POST /platform/v1/broadcasts (201) | Crear un borrador. Body {channel_id, name (≤200), template_name (≤512), language_code="es"}. Errores: 403 broadcasts_not_in_plan (free), 429 broadcasts_per_month_quota, 422 channel_not_found / channel_not_active / channel_not_whatsapp. |
GET /platform/v1/broadcasts | Listar tus broadcasts (más nuevo primero). |
GET /platform/v1/broadcasts/{id} | Detalle de uno (404 opaco si no es tuyo). |
POST /platform/v1/broadcasts/{id}/recipients | Cargar ≤1000 destinatarios por llamada. Devuelve {added, duplicates, errors[]}. Errores: 404, 409 status_locked (no está en draft). |
POST /platform/v1/broadcasts/{id}/schedule | Agendar para un timestamp futuro ({scheduled_at} ISO-8601, tolerancia de 60s de skew; un timestamp claramente pasado → 422). draft → scheduled. Errores: 404, 409 status_locked, 422 no_recipients. |
POST /platform/v1/broadcasts/{id}/cancel | scheduled → draft (limpia scheduled_at). Errores: 404, 409 status_locked. |
POST /platform/v1/broadcasts/{id}/send (202) | Pasar a sending; el worker lo toma en su próximo ciclo (cada 30s). Errores: 404, 409 status_locked, 422 no_recipients. |
GET /platform/v1/broadcasts/{id}/metrics | Agregado por destinatario (ver abajo). |
Cargar destinatarios
Section titled “Cargar destinatarios”Hasta 1000 por llamada (llamá varias veces para campañas más grandes,
dentro del cap del plan). Cada destinatario es {to, variables}:
{ "recipients": [ { "to": "59891234567", "variables": { "nombre": "Ana", "codigo": "PIZZA20" } }, { "to": "59899876543", "variables": { "nombre": "Luis", "codigo": "PIZZA20" } } ]}to: el teléfono del destinatario (cualquier formato; se normaliza a dígitos). Un teléfono inválido va aerrors[].variables: los parámetros del template. Solo valores planos string-coercibles: un valordictolistanidado devuelve 422 (el worker mapea cada valor a un parámetro posicional del componente BODY de Meta, en orden de inserción, constr(valor); una estructura anidada no tiene encoding válido en Meta).
La respuesta separa los tres resultados:
{ "added": 2, "duplicates": 0, "errors": [] }added: filas creadas.duplicates: destinatarios repetidos (dentro del batch o ya cargados; hay un UNIQUE(broadcast_id, to)).errors[]: teléfonos inválidos (invalid_phone) y los que sobrepasan el cap del plan (recipients_limit_reached).
Métricas
Section titled “Métricas”{ "total": 1000, "sent": 980, "failed": 20, "delivered": 940, "read": 610, "responded": 85, "pending": 0, "response_rate": 0.0867}| Campo | Significado |
|---|---|
total | Destinatarios cargados. |
sent | Enviados con éxito a Meta (incluye delivered / read / responded). |
failed | Fallaron (cuota de template, token, error de Meta). |
delivered | Meta confirmó entrega al device. |
read | El usuario leyó el mensaje. |
responded | El destinatario respondió después del envío. |
pending | Todavía sin enviar (total - sent - failed). |
response_rate | responded / sent (0.0 si sent == 0). |
delivered / read / responded se derivan del data plane uniendo por
(canal, meta_message_id); son consultas batcheadas, no una por destinatario.
El envío es asíncrono: POST /{id}/send devuelve 202 de inmediato y el
worker procesa la campaña en su ciclo de 30s, mandando el template por
destinatario y comiteando fila por fila (reanudable si el proceso muere a
mitad).
Entrega at-least-once. Si el proceso muere entre la llamada a Meta y el commit de la fila, ese destinatario queda
pendingy se reintenta en el próximo ciclo, así que puede recibir el template dos veces. La ventana es de milisegundos (round-trip a Meta). El UNIQUE(broadcast_id, to)evita filas duplicadas, no llamadas a Meta duplicadas. Diseñá tu template asumiendo at-least-once.
Próximo paso
Section titled “Próximo paso”- Templates: cómo crear y aprobar el template que vas a usar en la campaña.
- Webhooks normalizados: recibí
message.delivered/message.readen tiempo real en lugar de pollear las métricas.