Enviar un broadcast
Si quieres mandar una plantilla aprobada a muchos destinatarios a la vez (lista de contactos, segmento por etiqueta, o un conjunto explícito de IDs), usa la API de broadcasts. Para enviar a un único número, mira la guía Enviar una plantilla.
sent / failed, posibilidad de cancelar, y un único cargo de conversaciones validado contra tu cuota antes de empezar.Pre-requisitos
- Una API key de tu org con scope
messages:send(o scopes vacíos = acceso total). Andá aConfiguración → Integraciones → API Keysen el dashboard. - Una plantilla en estado
APPROVED. LasPENDINGoREJECTEDbloquean el envío. Listalas conGET /organizations/{orgId}/templates. - El
phone_number_id(UUID de Mosend) del número desde el que vas a enviar. Lo ves debajo de cada número en el dashboard, o víaGET /organizations/{orgId}/phone-numbers. - Los contactos deben tener
optInStatus !== 'OPTED_OUT'. Los que estén opted-out se filtran automáticamente del envío.
Asegúrate de tener los contactos cargados
Puedes crear contactos uno por uno, importar por CSV, o pasarlos por waId (teléfono en formato E.164 sin +) — la API hace upsert por waId. Cada contacto recibe un id (UUID) que es lo que usas después en el broadcast.
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/contacts' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>' \
-H 'Content-Type: application/json' \
-d '{
"waId": "573001234567",
"name": "Juan Pérez",
"language": "es"
}'
# Respuesta: { "data": { "id": "abc-uuid-...", ... } }
# El opt-in se gestiona aparte: POST /contacts/bulk-opt-in-status o el módulo opt-ins.Para volúmenes grandes (cientos / miles), usa POST /contacts/import con un array. Detalles en referencia de contacts.
Define la audiencia
El broadcast acepta tres formas de elegir destinatarios (puedes combinar lista + IDs):
Opción A · Lista de contactos
Crea una lista y agrégale miembros. Después pasas solo el listId al broadcast — ideal para audiencias recurrentes (clientes-vip, prospects-2026, etc.).
# 1. Crear la lista
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/contact-lists' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>' \
-H 'Content-Type: application/json' \
-d '{ "name": "Clientes Q1", "description": "Compradores ene-mar 2026" }'
# Respuesta: { "data": { "id": "<listId>", ... } }
# 2. Agregar contactos por id
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/contact-lists/<listId>/members' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>' \
-H 'Content-Type: application/json' \
-d '{ "contactIds": ["uuid-1", "uuid-2", "uuid-3"] }'Opción B · Lista poblada por etiqueta
Si ya etiquetas tus contactos (en el dashboard o vía POST /contacts/bulk-tag), puedes poblar una lista a partir de una etiqueta. Útil para segmentos dinámicos tipo "manda a todos los que tengan tag vip".
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/contact-lists/<listId>/add-by-tag' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>' \
-H 'Content-Type: application/json' \
-d '{ "tagId": "<tagId>" }'
# Respuesta: { "data": { "added": 47, "alreadyMembers": 3 } }Opción C · Pasar contactIds directo
Para envíos ad-hoc sin crear lista (p.ej. integración que arma la audiencia desde tu propio CRM), pasa un array de contactIds al crear el broadcast — sin listId.
Crea el broadcast
Esto solo lo crea (estado DRAFT o, si pasas scheduledAt, SCHEDULED). Aún no se manda nada. La audiencia se resuelve al disparar el envío en el paso 5.
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/broadcasts' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>' \
-H 'Content-Type: application/json' \
-d '{
"name": "Anuncio promo Q1",
"phoneNumberId": "<UUID-de-tu-numero>",
"templateId": "<UUID-de-la-plantilla>",
"templateLanguage": "es_CO",
"listId": "<listId>",
"contactIds": [],
"templateVariables": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Juan" }
]
}
]
}'
# Respuesta
# {
# "data": {
# "id": "<broadcastId>",
# "status": "DRAFT",
# "totalRecipients": 0, // se calcula al hacer /send
# ...
# }
# }templateVariablessigue el formato oficial de Meta — el mismo arraycomponentsque usas enPOST /messagesen modo experto. Si la plantilla no tiene variables, déjalo como[]u omítelo.- Plantillas con botones dinámicos (URL con
{{1}}oCOPY_CODE): el array tiene que incluir un component{ type: "button", sub_type: "url" | "copy_code", index: "0", parameters: [...] }además del body. Si lo omites, Meta rechaza con#131008. Ver el ejemplo OTP en /enviar-plantilla. - Las mismas variables aplican a todos los destinatarios. Para personalización por-contacto (cada uno con su nombre, su pedido, etc.) hoy hay que crear N broadcasts o usar
POST /messagesen loop — vamos a soportar variables por-contacto pronto. scheduledAt(ISO 8601) deja el broadcast enSCHEDULEDy Mosend lo dispara solo a esa hora — un job interno corre cada minuto y ejecuta losSCHEDULEDcuya hora ya pasó. No necesitas llamar/sendtú. Si quieres adelantarlo, puedes igual disparar/sendmanualmente; si quieres abortarlo antes de la hora, usa/cancel.
Dispáralo
POST /broadcasts/{id}/send resuelve la audiencia (deduplica entre listId + contactIds, descarta opt-outs), valida tu cuota de conversaciones del plan antes de empezar, y manda secuencialmente con throttle de ~50 msg/s. Retorna el resumen final.
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/broadcasts/<broadcastId>/send' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>'
# Respuesta (cuando termina)
# {
# "data": {
# "sent": 198,
# "failed": 2,
# "total": 200
# }
# }La request bloquea hasta que termine — un broadcast de 200 contactos a 50/s tarda ~4 segundos. Para volúmenes grandes (>500), aumentá tu timeout HTTP del lado del cliente o consultá el estado con polling al endpoint GET /broadcasts/{id}.
Sigue el progreso y los estados
Cada destinatario tiene su propio BroadcastRecipient con estado individual (PENDING / SENT / FAILED / DELIVERED / READ). El status del broadcast en sí queda en COMPLETED (al menos un sent) o FAILED (todos fallaron).
curl 'https://api.mosend.dev/organizations/{orgId}/broadcasts/<broadcastId>' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>'
# Respuesta
# {
# "data": {
# "id": "...",
# "status": "COMPLETED",
# "totalRecipients": 200,
# "sentCount": 198,
# "failedCount": 2,
# "startedAt": "...",
# "completedAt": "...",
# "counts": { // agregados listos para tu dashboard
# "total": 200,
# "sent": 198, // SENT + DELIVERED + READ (salió OK)
# "delivered": 180, // DELIVERED + READ (llegó al teléfono)
# "read": 95, // el destinatario lo abrió
# "failed": 2, // no les llegó
# "replied": 23 // contestaron tras recibirlo
# }
# }
# }Los counts son acumulativos por embudo: cada READ también cuenta como delivered y sent; cada DELIVERED cuenta como sent. replied marca a quienes te escribieron de vuelta dentro de los 30 días posteriores al envío.
Listar destinatarios por estado
Para traer el detalle por destinatario (sin cargar el broadcast entero) y filtrar por estado, usa GET /broadcasts/{id}/recipients. Acepta ?filter= con uno de replied · read · delivered · sent · failed, y pagina por cursor (?cursor=, ?limit= hasta 200). Sin filter devuelve todos.
curl 'https://api.mosend.dev/organizations/{orgId}/broadcasts/<broadcastId>/recipients?filter=replied&limit=100' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>'
# Respuesta
# {
# "data": {
# "items": [
# {
# "id": "<recipientId>",
# "contactId": "...",
# "contact": { "id": "...", "name": "Juan Pérez", "waId": "573001234567" },
# "status": "READ",
# "metaMessageId": "wamid....",
# "sentAt": "...", "deliveredAt": "...", "readAt": "...",
# "repliedAt": "2026-05-20T14:02:11.000Z"
# }
# ],
# "nextCursor": "<recipientId>" // null cuando no hay más
# }
# }replied→ contestaron (repliedAtno nulo).read→ estadoREAD.delivered→DELIVEREDoREAD.sent→SENT,DELIVEREDoREAD(salió OK).failed→ no les llegó (FAILED), conerrorMessage.
Para enterarte del cambio SENT → DELIVERED → READ de cada mensaje individual en tiempo real (que llega después, vía webhook de Meta), suscríbete al evento message.status en webhooks salientes. El payload firmado con HMAC trae el messageId que enlazas con el recipient correspondiente. Mosend también propaga esos estados al BroadcastRecipient, así que los counts de arriba se actualizan solos.
Seguimiento (segundo toque)
Para re-impactar a quienes no se engancharon, crea un seguimiento: una difusión cuya audiencia se deriva de los destinatarios de otra. Pasa sourceBroadcastId + followUpAudience en vez de listId/contactIds. Debe usar el mismo número que la difusión original.
NOT_ENGAGED— no leyeron ni respondieron (status SENT/DELIVERED, sin reply).NOT_REPLIED— no respondieron (aunque hayan leído).NOT_DELIVERED— no les llegó (FAILED) — reintento.
La audiencia se resuelve al enviar (no al crear): quien lea o conteste mientras tanto queda excluido automáticamente. Como va fuera de la ventana de 24 h, el seguimiento también usa una plantilla aprobada (un recordatorio). Prográmalo 24–48 h después con scheduledAt.
curl -X POST 'https://api.mosend.dev/organizations/{orgId}/broadcasts' \
-H 'X-Api-Key: mk_live_<prefix>.<secret>' \
-H 'Content-Type: application/json' \
-d '{
"name": "Recordatorio promo Q1",
"phoneNumberId": "<UUID-del-mismo-numero>",
"templateId": "<UUID-plantilla-recordatorio>",
"sourceBroadcastId": "<broadcastId-original>",
"followUpAudience": "NOT_ENGAGED",
"scheduledAt": "2026-05-23T14:00:00.000Z"
}'Cuota y cancelación
- Antes de empezar a enviar, el backend calcula cuántas conversaciones nuevas implica tu broadcast (destinatarios sin hilo activo en el periodo actual) y lo compara con tu cuota del plan. Si no alcanza y no tienes política de overage activa, la request rechaza con
400antes de marcarSENDING— no quedas a medias. - Para abortar un broadcast en curso,
POST /broadcasts/{id}/cancel. Los que ya se mandaron no se desmandan (Meta no lo permite), pero los pendientes no se envían. - Los opt-outs se filtran automáticamente del envío — no necesitas limpiar la lista manualmente. Si un contacto se opta-out después de crear el broadcast pero antes de
/send, queda fuera.
Errores comunes
- 400 "Audiencia requerida (lista o contactos)": no pasaste
listIdnicontactIds. Necesitas al menos uno (o ambos — se mergean y deduplican). - 400 "La plantilla debe estar APROBADA": la plantilla está en
PENDINGoREJECTED. Espera a que Meta la apruebe (AUTHENTICATION suelen tardar minutos, MARKETING horas). - 400 "Este broadcast abriría N conversación(es) nueva(s)…": chocaste con tu cuota del plan. Contrata un add-on, sube de plan, activa overage, o reduce destinatarios.
- 403 con API key sin scope: tu key tiene scopes restringidos y no incluyen
messages:send. Recréala conscopes: [](acceso total) o suma el scope. - recipient FAILED con error Meta 131056: ese contacto nunca confirmó opt-in con tu negocio (Meta lo bloquea aunque tu API lo tenga
OPTED_IN). Pídeles que escriban primero a tu número o que opt-in vía un canal de Meta.
Referencias rápidas
- /api/broadcasts — todos los endpoints del módulo
- /api/contact-lists — gestión de listas y miembros
- /api/contacts — alta, edición, etiquetado bulk
- /api/tags — manejo de etiquetas para segmentación
- /api/templates — listar / crear plantillas