---
title: "Enviar mensajes de WhatsApp por API: cURL, Node, Python y PHP"
description: "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."
canonical: "https://omnichannels.ai/es/blog/send-whatsapp-message-api-examples/"
language: "es"
updated: "2026-10-16"
publisher: "BroadNet Technologies"
brand: "OmniChannels.ai"
category: "integration"
primaryQuery: "enviar mensaje whatsapp api"
keywords: ["ejemplo api whatsapp curl", "enviar plantilla whatsapp node.js", "api whatsapp python", "api whatsapp php", "enviar mensajes api whatsapp business", "mensaje de sesión whatsapp api", "api whatsapp multipart form-data"]
published: "2026-10-16"
author: "OmniChannels.ai Editorial Team"
reviewer: "BroadNet Messaging Solutions Team"
readingMinutes: 10
answer: "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."
takeaways: ["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."]
faq: 
  - q: "¿Envío los mensajes de WhatsApp como JSON o como datos de formulario?"
    a: "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."
  - q: "¿Puedo enviar un mensaje de WhatsApp sin plantilla?"
    a: "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á."
  - q: "¿Cómo sé si el mensaje se entregó?"
    a: "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."
  - q: "¿Puedo enviar a muchos destinatarios en una sola petición?"
    a: "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."
  - q: "¿Hay una clave de idempotencia para evitar envíos duplicados?"
    a: "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."
  - q: "¿Necesito opt-in antes de enviar?"
    a: "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."
sources: 
  - title: "WhatsApp Business Messaging Policy"
    url: "https://business.whatsapp.com/policy"
  - title: "Pricing on the WhatsApp Business Platform — Meta for Developers"
    url: "https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing"
  - title: "RFC 7578: Returning Values from Forms: multipart/form-data"
    url: "https://www.rfc-editor.org/rfc/rfc7578"
---

# Enviar mensajes de WhatsApp por API: cURL, Node, Python y PHP

Canonical: https://omnichannels.ai/es/blog/send-whatsapp-message-api-examples/
Language: es
Publisher: OmniChannels.ai by BroadNet Technologies

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](/developers/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](/developers/quickstart/) 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](/es/blog/whatsapp-24-hour-customer-service-window/) explica la mecánica, y nuestra [página de precios](/es/whatsapp-pricing/) y la [calculadora](/es/tools/whatsapp-pricing-calculator/) 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.

```bash
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
```

```js
// 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());
```

```python
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
<?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:

```json
{
  "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](/developers/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.

```bash
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](/es/blog/otp-authentication-messages-guide/) 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`.

```bash
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.

```bash
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:

```bash
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.

```bash
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](/es/blog/whatsapp-webhooks-guide/). 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](/developers/errors-and-limits/): 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](/developers/whatsapp/) o el [inicio rápido](/developers/quickstart/).
