Cómo enviar una plantilla de WhatsApp por la API de Waspy
El envío es POST /api/v2/messages con type template, language.code y los parámetros del body. Listá con status=approved, en minúscula. Crear la plantilla no está en la API.
Respuesta corta: para mandar una plantilla ya aprobada hacés POST https://api.waspytech.com/api/v2/messages con type: "template", template.language.code y, si el cuerpo tiene {{1}}, components de tipo body con parameters. Scope messages:write. La respuesta 201 trae status: "queued". Crear la plantilla no es un endpoint de la API v2: se crea en el panel o en Meta Business Manager.
La guía de envío está en send template. La referencia de los GET está en templates. Lo que la API no hace está en limitaciones. Cómo listarlas, sin repetir el paginado acá, está en listar plantillas.
Crear no es un POST
| Querés | Dónde |
|---|---|
| Dar de alta, editar o borrar el texto | Panel de Waspy o Meta Business Manager |
| Ver si Meta ya la aprobó | GET /api/v2/templates |
Mandar una que ya está approved | POST /api/v2/messages |
POST /templates. La palabra “crear plantilla por API” en una búsqueda describe el alta. En la v2 ese alta no existe. Si tu integración necesita el nombre aviso_despacho en es_AR, alguien lo carga en el panel y espera a que status sea approved. No hay webhook template.approved: se vuelve a consultar.
Listar solo las aprobadas
curl -sS "https://api.waspytech.com/api/v2/templates?status=approved&pageSize=50" \
-H "Authorization: Bearer $WASPY_API_KEY"
Scope templates:read. page arranca en 1 y pageSize en 20, con tope 100. phoneNumberId filtra por canal (el UUID de Waspy, el de GET /channels).
El query status se compara igual, sin pasar a minúscula. Los valores que hay en la base son:
| Valor | Sirve para enviar |
|---|---|
approved | Sí, si el nombre y el idioma coinciden con lo que Meta tiene |
pending | No. Meta todavía no la aprobó |
rejected | No |
paused | No |
disabled | No |
status=APPROVED. Con el código de hoy eso no filtra: no hay fila APPROVED, la lista vuelve vacía y el HTTP es 200. La referencia usa approved. Usá esa.
Sin phoneNumberId, y sin includeInactive=true, el listado oculta plantillas de canales que no están active. Si acabás de conectar el número y no aparece, revisá el estado del canal antes de asumir que Meta la rechazó.
Resolver por nombre
curl -sS "https://api.waspytech.com/api/v2/templates/by-name/aviso_despacho?language=es_AR" \
-H "Authorization: Bearer $WASPY_API_KEY"
Devuelve el match más nuevo. phoneNumberId desambigua si el mismo nombre existe en otro canal. Si no hay fila, es 404 TEMPLATE_NOT_FOUND (Template not found). Ese código es de este GET y de GET /templates/:id (el :id es un UUID). No es la respuesta del envío.
En la fila, status viene en minúscula (approved) y category en mayúscula (UTILITY, MARKETING, AUTHENTICATION). language es un string (es_AR), no el objeto { code } que pide el POST.
El POST
curl -sS -X POST https://api.waspytech.com/api/v2/messages \
-H "Authorization: Bearer $WASPY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-1847-despacho" \
-d '{
"phoneNumberId": "CHANNEL_UUID",
"to": "+5491126032641",
"type": "template",
"clientRef": "pedido-1847",
"template": {
"name": "aviso_despacho",
"language": { "code": "es_AR" },
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "María" },
{ "type": "text", "text": "#1847" }
]
}
]
}
}'
type del componente va en minúscula (body). El orden de parameters es {{1}}, {{2}}, … El phoneNumberId es el UUID del canal en Waspy, no el id de Meta. to termina en E.164: si después de normalizar no es un número de WhatsApp válido, es 400 INVALID_PHONE.
La respuesta 201 trae data.id, data.status: "queued" y waMessageId: null. El id de Meta aparece cuando el estado pasa a sent. Cómo seguirlo está en estados del mensaje.
Idempotency-Key es opcional, máximo 256 caracteres, y vive 24 horas por cuenta. Si repetís la misma clave después de un 201, vuelve la respuesta guardada con el header Idempotency-Replayed: true y no se manda de nuevo. Si la primera todavía está en curso (el lock dura 30 segundos), es 409 IDEMPOTENCY_CONFLICT. clientRef es otra cosa: también hasta 256 caracteres, y vuelve en cada webhook de estado. Podés usar el número de pedido en los dos, con roles distintos.
Una plantilla no pasa por la ventana de 24 horas. SERVICE_WINDOW_EXPIRED (403) es para texto, foto o documento cuando el cliente no escribió. El canal inexistente es 400 INVALID_CHANNEL. El canal que existe pero no está active es 403 CHANNEL_NOT_CONNECTED.
Si el nombre o los parámetros no cierran
El POST no busca status === approved antes de encolar. Si no hay fila local con ese nombre e idioma, igual persiste el envío y lo manda a Meta. El rechazo llega asincrónico:
| Qué ves | Cuándo |
|---|---|
404 TEMPLATE_NOT_FOUND | GET /templates/:id o GET /templates/by-name/:name sin fila. No es el POST |
| Meta 132000 | La cantidad de parámetros no coincide con la plantilla. El mensaje queda failed |
| Meta 132001 | La plantilla no existe o no está aprobada en Meta |
TEMPLATE_NOT_FOUND como error del envío. En el código de la ruta, ese código no sale del POST. Consultá el GET antes, y después leé errorCode en el webhook o en GET /messages/:id.
Un header con imagen no se sube por la API v2. Limitaciones lo dice: el upload no está expuesto. En el mensaje va link (URL pública) o un id de media de Meta. El cuerpo de esta nota es el caso de variables de texto.
Plan, cupo y tarifa
La API REST está en Scale (US$150 por mes) y Enterprise. Starter (US$70) y Growth (US$100) no incluyen la clave. Diez días de prueba, sin tarjeta, en el registro. El detalle está en Planes y en la página de la API.
Meta cobra el mensaje entregado, no el 201. En Argentina, desde el 1 de octubre de 2026, una utilidad sale alrededor de ARS 37,68 (~US$0,026) y un marketing alrededor de ARS 89,56 (~US$0,0618). Waspy no suma un porcentaje. La fuente es la rate card y precios de la plataforma. El desglose está en precios de la API.
El cupo de la API es 120 pedidos por minuto por cuenta, compartido por todas las claves. Un envío y un GET de plantillas suman al mismo contador. El 429 y el Retry-After están en rate limits.
