Enviar mensajes de WhatsApp por API: cURL, Node, Python y PHP
Cómo enviar un mensaje de WhatsApp por API: ejemplos en cURL, Node.js, Python y PHP para plantillas y mensajes de sesión, con seguimiento de estados y errores.
Por OmniChannels.ai Editorial Team · Revisado por BroadNet Messaging Solutions Team
Integración
Respuesta breve
Con la API de OmniChannels.ai, usted envía todos los mensajes de WhatsApp a través de un único endpoint, POST https://api.broadnet.me/whatsapp/message/sendbulk, como datos de formulario multipart con una cabecera Authorization: Bearer. El campo messageType selecciona la carga: TEMPLATE para plantillas aprobadas, que necesita para iniciar una conversación o escribir fuera de la ventana de 24 horas, y TEXT, IMAGE o INTERACTIVE para responder dentro de ella.
En esta página
- ¿Cómo envío un mensaje de WhatsApp a través de la API?
- Plantilla o mensaje de sesión: ¿cuál envío?
- ¿Cómo envío una plantilla aprobada?
- ¿Qué campos de la petición son los más importantes?
- ¿Cómo envío una plantilla con un botón o un OTP?
- ¿Cómo respondo dentro de la ventana de atención al cliente?
- ¿Cómo hago el seguimiento de la entrega?
- ¿Qué errores debo gestionar?
- Ejemplo práctico: un servicio de notificación de pedidos
- ¿Cuáles son los errores habituales?
- Cómo le ayuda OmniChannels.ai
Puntos clave
- Todos los mensajes de WhatsApp pasan por POST /whatsapp/message/sendbulk; messageType (TEMPLATE, TEXT, IMAGE, INTERACTIVE y otros) decide qué campos son obligatorios.
- La petición es multipart/form-data, no JSON, por lo que Python necesita files= y PHP necesita un cuerpo en forma de array para evitar la codificación de URL.
- Fuera de la ventana de atención al cliente de 24 horas solo se puede enviar una plantilla aprobada; dentro de ella se puede enviar texto libre, contenido multimedia y mensajes interactivos.
- La llamada pone los mensajes en cola y devuelve de inmediato un batchId; los estados por destinatario llegan a su webhook y desde el endpoint bulk-insights.
- La API no admite clave de idempotencia, así que registre cada envío antes de hacerlo y compruebe el estado tras un tiempo de espera agotado en lugar de reenviar.
Esta guía está dirigida a los desarrolladores que integran el envío de WhatsApp en una aplicación. Muestra peticiones funcionales en cURL, Node.js, Python y PHP, explica la diferencia entre los mensajes de plantilla y de sesión, y trata el seguimiento y los errores. Todos los endpoints y campos proceden de nuestra referencia de la API de WhatsApp y de la especificación OpenAPI.
¿Cómo envío un mensaje de WhatsApp a través de la API?
Haga un único POST autenticado a https://api.broadnet.me/whatsapp/message/sendbulk con campos de formulario. El campo messageType elige el tipo de mensaje, y la respuesta confirma que los mensajes se pusieron en cola.
| Elemento | Valor |
|---|---|
| URL base | https://api.broadnet.me (sin prefijo /v1) |
| Endpoint | POST /whatsapp/message/sendbulk |
| Autenticación | Authorization: Bearer $BROADNET_TOKEN |
| Tipo de contenido | multipart/form-data (lo establece automáticamente su cliente HTTP) |
| Respuesta | Resumen JSON del lote con un batchId |
Las credenciales de prueba se emiten tras una breve solicitud de acceso, y los envíos de prueba van a sus propios números verificados. Consulte el inicio rápido y las páginas de autenticación. Mantenga el token solo en su servidor.
Plantilla o mensaje de sesión: ¿cuál envío?
Envíe una plantilla para iniciar una conversación o para escribir fuera de la ventana, y un mensaje de sesión cuando el cliente le haya escrito en las últimas 24 horas. Esta regla la fija Meta, no nosotros.
| Mensaje de plantilla | Mensaje de sesión | |
|---|---|---|
messageType |
TEMPLATE |
TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, INTERACTIVE y otros |
| Cuándo se permite | En cualquier momento, una vez aprobada la plantilla | Solo mientras la ventana de atención al cliente esté abierta |
| Requiere aprobación | Sí, de Meta | No |
| Uso típico | Actualizaciones de pedidos, OTP, recordatorios, campañas | Respuestas de soporte, respuestas a preguntas |
Meta cobra por mensaje entregado según la categoría, y sus normas para los mensajes de servicio y las plantillas de utilidad cambiaron el 1 de octubre de 2026. Nuestra guía de la ventana explica la mecánica, y nuestra página de precios y la calculadora tratan el costo.
¿Cómo envío una plantilla aprobada?
Establezca messageType=TEMPLATE, indique el nombre de la plantilla y su código de idioma, y pase los valores de las variables. Cree batchId=0 para iniciar un lote nuevo.
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
-H "Authorization: Bearer $BROADNET_TOKEN" \
-F batchId=0 \
-F campaignName=order-updates \
-F destinationNo=971500000000 \
-F messageType=TEMPLATE \
-F templateName=order_update \
-F templateLanguage=en_US \
-F bodyTemplateVariables=INV-400
// Node.js 18+ (fetch y FormData integrados)
const form = new FormData();
form.append("batchId", "0");
form.append("campaignName", "order-updates");
form.append("destinationNo", "971500000000");
form.append("messageType", "TEMPLATE");
form.append("templateName", "order_update");
form.append("templateLanguage", "en_US");
form.append("bodyTemplateVariables", "INV-400");
const res = await fetch("https://api.broadnet.me/whatsapp/message/sendbulk", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.BROADNET_TOKEN}` }, // fetch establece el límite (boundary) del multipart
body: form,
});
console.log(await res.json());
import os
import requests
fields = {
"batchId": "0",
"campaignName": "order-updates",
"destinationNo": "971500000000",
"messageType": "TEMPLATE",
"templateName": "order_update",
"templateLanguage": "en_US",
"bodyTemplateVariables": "INV-400",
}
# Envíe cada campo como una parte multipart (files=) y no codificado en URL (data=).
r = requests.post(
"https://api.broadnet.me/whatsapp/message/sendbulk",
headers={"Authorization": f"Bearer {os.environ['BROADNET_TOKEN']}"},
files={k: (None, v) for k, v in fields.items()},
timeout=30,
)
print(r.json())
<?php
$ch = curl_init("https://api.broadnet.me/whatsapp/message/sendbulk");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BROADNET_TOKEN")],
// Un array (no una cadena) hace que cURL envíe multipart/form-data.
CURLOPT_POSTFIELDS => [
"batchId" => "0",
"campaignName" => "order-updates",
"destinationNo" => "971500000000",
"messageType" => "TEMPLATE",
"templateName" => "order_update",
"templateLanguage" => "en_US",
"bodyTemplateVariables" => "INV-400",
],
]);
echo curl_exec($ch);
curl_close($ch);
El destino es el prefijo del país más el número, solo dígitos. La llamada responde de inmediato con un resumen del lote:
{
"totalFiles": 1,
"totalSuccess": 1,
"totalFailed": 0,
"totalDuplicates": 0,
"batchId": "2609091405000000001",
"fileResults": [
{
"fileName": "direct",
"totalRecords": 1,
"successCount": 1,
"failedRecipients": [],
"duplicateRecipients": []
}
],
"message": "Messages queued for delivery"
}
Guarde el batchId junto a su propio registro. "En cola" no es "entregado".
¿Qué campos de la petición son los más importantes?
Los campos obligatorios dependen de messageType. Estos son los habituales.
| Campo | Obligatorio | Descripción |
|---|---|---|
messageType |
Sí | TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, STICKER, LOCATION, CONTACTS, TEMPLATE, INTERACTIVE, REACTION o FLOW |
destinationNo o destinationFiles |
Uno de los dos | Números separados por comas, puntos y comas o saltos de línea; o un archivo TXT, CSV o XLSX |
batchId |
No | 0 inicia un lote nuevo |
campaignName |
No | Su etiqueta para los informes |
allowDuplicate |
No | false elimina los destinatarios duplicados |
templateName, templateLanguage |
Para TEMPLATE |
Nombre de la plantilla aprobada y código de idioma, como en_US o ar |
bodyTemplateVariables |
Según la plantilla | Valor de la variable del cuerpo; los parámetros con nombre se pasan como JSON |
buttonsTemplateVariable |
Según la plantilla | Valor dinámico del botón: sufijo de URL, código de cupón o código OTP |
content |
Para TEXT |
Cuerpo del texto, de hasta 4.096 caracteres, o un pie de foto para contenido multimedia |
mediaUrl |
Según el tipo | URL HTTPS pública del contenido multimedia |
interactivePayload |
Para INTERACTIVE |
Objeto interactivo de Meta como cadena JSON |
La programación usa scheduled, scheduleTime y gmt, y delay regula la entrega. La lista completa está en la referencia de la API de WhatsApp.
¿Cómo envío una plantilla con un botón o un OTP?
Añada el valor del botón en buttonsTemplateVariable. Para una plantilla de autenticación con un botón de copiar código, pase el código tanto en el campo del cuerpo como en el del botón.
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
-H "Authorization: Bearer $BROADNET_TOKEN" \
-F batchId=0 \
-F destinationNo=971500000000 \
-F messageType=TEMPLATE \
-F templateName=login_code \
-F templateLanguage=en_US \
-F bodyTemplateVariables=482913 \
-F buttonsTemplateVariable=482913
Aquí login_code es un marcador de posición para su propia plantilla de autenticación aprobada. Para varios botones, buttonsTemplateVariable admite un array JSON. Genere el código en su servidor, guárdelo con hash y con caducidad, y no lo registre nunca en logs. Nuestra guía de OTP trata la entrega con un toque, sin toque y con copia de código.
¿Cómo respondo dentro de la ventana de atención al cliente?
Use un tipo de mensaje de sesión. Una respuesta de texto sencillo necesita messageType=TEXT y content.
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
-H "Authorization: Bearer $BROADNET_TOKEN" \
-F batchId=0 \
-F destinationNo=971500000000 \
-F messageType=TEXT \
-F campaignName=support \
-F "content=Your order has shipped. Reply here if you need anything."
Para los botones de respuesta, envíe messageType=INTERACTIVE y un interactivePayload que siga el formato de mensajes interactivos de Meta.
curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
-H "Authorization: Bearer $BROADNET_TOKEN" \
-F batchId=0 \
-F destinationNo=971500000000 \
-F messageType=INTERACTIVE \
-F campaignName=support \
-F 'interactivePayload={"type":"button","body":{"text":"Confirm your delivery slot?"},"action":{"buttons":[{"type":"reply","reply":{"id":"slot_ok","title":"Confirm"}},{"type":"reply","reply":{"id":"slot_change","title":"Change"}}]}}'
Para marcar un mensaje entrante como leído y, opcionalmente, mostrar un indicador de escritura, llame a POST /whatsapp/message/read con JSON:
curl -X POST https://api.broadnet.me/whatsapp/message/read \
-H "Authorization: Bearer $BROADNET_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "messageId": "wamid.HBgMOTcxNTAwMDAwMDAwFQIAEhgg", "typing": true }'
Consulte nuestra guía de mensajes interactivos para orientarse en el diseño.
¿Cómo hago el seguimiento de la entrega?
Los estados se envían al webhook configurado en su cuenta y también pueden consultarse por lote. Cada destinatario pasa por SUBMITTED, SENT, DELIVERED y READ, o termina como FAILED o REJECTED con un motivo.
curl "https://api.broadnet.me/whatsapp/message/bulk-insights?batch_Id=2609091405000000001&status=FAILED&page=0&size=50" \
-H "Authorization: Bearer $BROADNET_TOKEN"
batch_Id es obligatorio; status, destination, search, page y size son opcionales, y page empieza en cero. Use la consulta periódica (polling) como respaldo ante eventos perdidos y construya su receptor como se describe en nuestra guía de webhooks. Los lotes también pueden pausarse, reanudarse y abortarse con los endpoints de lotes de la referencia.
¿Qué errores debo gestionar?
Lea primero el estado HTTP y después el cuerpo del error. La API genera los errores en un cuerpo JSON de cinco campos, y los fallos específicos de WhatsApp pueden proceder de Meta.
| Estado | Significado | Acción |
|---|---|---|
400 |
Error de validación, como un campo específico del tipo ausente o un destino no válido | Corrija la petición; no reintente sin cambios |
401 / 403 |
Token ausente o no válido, u operación no permitida | Corrija el token o pídanos que habilitemos la operación |
404 |
Plantilla o remitente no encontrado | Compruebe el nombre y el idioma de la plantilla |
413 |
Archivo cargado demasiado grande | Reduzca el archivo |
429 |
Límite de frecuencia | Espere según Retry-After |
502 |
Meta rechazó la llamada; message incluye el código y el texto de error de Meta |
Léalo; reintente solo si es transitorio |
500, 503, 504 |
Problema del servidor o del servicio previo | Reintente con espera exponencial y jitter |
Nuestra guía de códigos de error explica los códigos habituales de Meta. Como no hay clave de idempotencia, siga los pasos de protección frente a duplicados de Errores y límites: escriba primero un registro pendiente, guarde el batchId devuelto y, tras un tiempo de espera agotado, compruebe el estado antes de volver a enviar.
Ejemplo práctico: un servicio de notificación de pedidos
"Souq Rafiq" (ficticia), un comercio de los Emiratos Árabes Unidos, envía una plantilla order_update cuando se despacha un pedido.
- El servicio de pedidos escribe una fila con la clave
order-8842-shippedy el estadopending. - Llama a
sendbulkcon la plantilla y el número de pedido comobodyTemplateVariables. - Con un
200, guarda elbatchIdy marca la fila comoaccepted. - El gestor del webhook actualiza la fila a
DELIVEREDoREAD, o aFAILEDcon un motivo. - Si el cliente responde, la herramienta de soporte usa un mensaje de sesión
TEXTdentro de la ventana. - Un proceso nocturno lee
bulk-insightsde los lotes del día anterior y concilia los estados que falten.
¿Cuáles son los errores habituales?
- Enviar JSON. El endpoint de envío de WhatsApp es multipart;
Content-Type: application/jsonno funcionará. - Usar
data=en Python en lugar defiles=, lo que codifica el formulario en URL. - Texto libre fuera de la ventana, que falla. Use una plantilla.
- Código de idioma incorrecto, como
enen lugar delen_USde la plantilla. - Tratar un
200como entregado. Significa en cola. - Reintentos a ciegas tras un tiempo de espera agotado, que pueden enviar dos veces un mensaje a un cliente.
- El token en código de cliente. Llame a la API solo desde su backend.
- Límites: los detalles de la carga varían según la plantilla. Confirme con nuestro equipo la disposición de las variables para su primera campaña personalizada.
Cómo le ayuda OmniChannels.ai
BroadNet es un Meta Business Solution Provider oficial, y el alta en la API de WhatsApp Business usa el Embedded Signup de Meta; nuestro equipo prepara el registro con usted, mientras que las aprobaciones siguen en manos de Meta. La API REST está documentada con una especificación OpenAPI 3.1 y un SDK de TypeScript, @broadnet/cpaas-sdk, y los estados de entrega se normalizan entre canales. Empiece con la documentación de la API de WhatsApp o el inicio rápido.
Preguntas frecuentes
¿Envío los mensajes de WhatsApp como JSON o como datos de formulario?
Como datos de formulario. El endpoint de envío de WhatsApp, POST /whatsapp/message/sendbulk, admite multipart/form-data, con cada campo como una parte del formulario. Los envíos individuales de SMS usan JSON, lo que es una fuente habitual de confusión. En Python, envíe los campos con files= en lugar de data=, y en PHP pase un array como cuerpo para que cURL construya el multipart.
¿Puedo enviar un mensaje de WhatsApp sin plantilla?
Solo dentro de la ventana de atención al cliente. Cuando un cliente le escribe, se abre una ventana de 24 horas y puede responder con texto, contenido multimedia o mensajes interactivos. Fuera de ella, o para iniciar una conversación, debe enviar una plantilla aprobada. La API aplica las normas de Meta, y un envío de texto libre a una ventana cerrada fallará.
¿Cómo sé si el mensaje se entregó?
La llamada de envío solo confirma que los mensajes se pusieron en cola y devuelve un batchId. Los estados por destinatario pasan por SUBMITTED, SENT, DELIVERED y READ, o terminan como FAILED o REJECTED. Se envían a su webhook, y también puede consultarlos por lote en el endpoint bulk-insights.
¿Puedo enviar a muchos destinatarios en una sola petición?
Sí. destinationNo admite varios números separados por comas, puntos y comas o saltos de línea, y destinationFiles admite un archivo TXT, CSV o XLSX. Para una plantilla con variables distintas por destinatario, cargue un archivo personalizado con una fila por destinatario. Establezca allowDuplicate en false para eliminar los números duplicados.
¿Hay una clave de idempotencia para evitar envíos duplicados?
No. La API no acepta una clave de idempotencia. Si una petición agota el tiempo de espera, desde el cliente no puede saber si fue aceptada. Registre cada envío en su propia base de datos antes de llamar, guarde el batchId devuelto y compruebe el estado o la lista de lotes antes de reintentar.
¿Necesito opt-in antes de enviar?
Sí. La política de Meta exige que cada destinatario haya dado su opt-in para recibir mensajes de su empresa, y las plantillas deben estar aprobadas antes de usarse. La API no comprueba el consentimiento por usted. Recabe el opt-in en el origen, conserve las pruebas y excluya a quien se dé de baja.
Fuentes
Siga leyendo
Más guíasIntegración
Guía de webhooks de WhatsApp: eventos, seguridad y reintentos
Cómo funcionan los webhooks de WhatsApp: eventos de mensajes y de estado, un gestor fiable, la firma de Meta, reintentos, idempotencia y eventos perdidos.
10 min de lectura
Integración
Enviar SMS por API: ejemplos en Python, Node.js, PHP y cURL
Envíe SMS por API en Python, Node.js, PHP y cURL: envíos individuales y masivos, Unicode y árabe, conteo de segmentos, webhooks de entrega y errores.
10 min de lectura
WhatsApp
OTP por WhatsApp o por SMS: costo, entrega y respaldo
OTP por WhatsApp o por SMS: modelo de costos, entrega, experiencia de usuario, plantillas de autenticación, respaldo a SMS y controles contra el fraude.
10 min de lectura
