Enviar un broadcast
Si querés mandar una plantilla aprobada a muchos destinatarios a la vez (lista de contactos, segmento por etiqueta, o un conjunto explícito de IDs), usá la API de broadcasts. Para enviar a un único número, mirá 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.
Asegurate de tener los contactos cargados
Podés 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 usás 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), usá POST /contacts/import con un array. Detalles en referencia de contacts.
Definí la audiencia
El broadcast acepta tres formas de elegir destinatarios (podés combinar lista + IDs):
Opción A · Lista de contactos
Creá una lista y agregale miembros. Después pasás 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 etiquetás tus contactos (en el dashboard o vía POST /contacts/bulk-tag), podés poblar una lista a partir de una etiqueta. Útil para segmentos dinámicos tipo "mandá 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), pasá un array de contactIds al crear el broadcast — sin listId.
Creá el broadcast
Esto solo lo crea (estado DRAFT o, si pasás 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 usás enPOST /messagesen modo experto. Si la plantilla no tiene variables, dejalo como[]o omitilo.- 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 omitís, 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 necesitás llamar/sendvos. Si querés adelantarlo, podés igual disparar/sendmanualmente; si querés abortarlo antes de la hora, usá/cancel.
Disparalo
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}.
Seguí 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, usá 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), suscribite al evento message.status en webhooks salientes. El payload firmado con HMAC trae el messageId que enlazás 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, creá un seguimiento: una difusión cuya audiencia se deriva de los destinatarios de otra. Pasá 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). Programalo 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 tenés política de overage activa, la request rechaza con
400antes de marcarSENDING— no quedás 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 necesitás 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. Necesitás al menos uno (o ambos — se mergean y deduplican). - 400 "La plantilla debe estar APROBADA": la plantilla está en
PENDINGoREJECTED. Esperá 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. Contratá un add-on, subí de plan, activá overage, o reducí destinatarios.
- 403 con API key sin scope: tu key tiene scopes restringidos y no incluyen
messages:send. Recreala conscopes: [](acceso total) o sumá 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). Pediles 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