Saltar al contenido
OmniChannels.ai

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.

Actualizado 10 min de lectura

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
  1. ¿Cómo envío un mensaje de WhatsApp a través de la API?
  2. Plantilla o mensaje de sesión: ¿cuál envío?
  3. ¿Cómo envío una plantilla aprobada?
  4. ¿Qué campos de la petición son los más importantes?
  5. ¿Cómo envío una plantilla con un botón o un OTP?
  6. ¿Cómo respondo dentro de la ventana de atención al cliente?
  7. ¿Cómo hago el seguimiento de la entrega?
  8. ¿Qué errores debo gestionar?
  9. Ejemplo práctico: un servicio de notificación de pedidos
  10. ¿Cuáles son los errores habituales?
  11. 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.

  1. El servicio de pedidos escribe una fila con la clave order-8842-shipped y el estado pending.
  2. Llama a sendbulk con la plantilla y el número de pedido como bodyTemplateVariables.
  3. Con un 200, guarda el batchId y marca la fila como accepted.
  4. El gestor del webhook actualiza la fila a DELIVERED o READ, o a FAILED con un motivo.
  5. Si el cliente responde, la herramienta de soporte usa un mensaje de sesión TEXT dentro de la ventana.
  6. Un proceso nocturno lee bulk-insights de 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/json no funcionará.
  • Usar data= en Python en lugar de files=, lo que codifica el formulario en URL.
  • Texto libre fuera de la ventana, que falla. Use una plantilla.
  • Código de idioma incorrecto, como en en lugar del en_US de la plantilla.
  • Tratar un 200 como 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

  1. WhatsApp Business Messaging Policy
  2. Pricing on the WhatsApp Business Platform — Meta for Developers
  3. RFC 7578: Returning Values from Forms: multipart/form-data