Plantillas
Una plantilla es un mensaje aprobado previamente por Meta. Es la única forma de escribir a alguien fuera de la ventana de 24 horas.
Las plantillas pertenecen a la WABA (cuenta de empresa), no a un número — todos los números de la misma cuenta comparten la misma biblioteca.
Categorías
| Categoría | Para qué | Coste |
|---|---|---|
UTILITY | Confirmación, actualización de pedido, recordatorio de cita | bajo |
AUTHENTICATION | Código de verificación | bajo |
MARKETING | Promoción, novedad, reenganche | más alto |
Meta reclasifica las plantillas que considera mal categorizadas. Una «utilitaria» que
en la práctica vende pasa a MARKETING, y el coste sube. allowCategoryChange: true (el
valor por defecto) deja que eso ocurra en lugar de rechazar la plantilla.
Ciclo de vida
La aprobación tarda de minutos a horas. El resultado llega por el webhook
message_template_status_update — no lo consultes en bucle.
PAUSED ocurre cuando muchos clientes bloquean o denuncian mensajes de esa plantilla.
Vuelve sola si la calidad se recupera; si empeora, pasa a DISABLED y deja de enviarse.
Variables y ejemplos
Toda plantilla con variables debe declarar ejemplos — sin ellos Meta la rechaza. Joinotify
lo valida antes de enviar y devuelve 422 con el campo issues señalando el
problema, lo que evita el ciclo lento de enviar, esperar y ser rechazado.
Formato posicional
{
"name": "confirmacao_pedido",
"category": "UTILITY",
"language": "pt_BR",
"components": [
{
"type": "BODY",
"text": "Hola {{1}}, tu pedido {{2}} está confirmado y llega en {{3}}.",
"example": { "body_text": [["María", "1042", "2 días hábiles"]] }
}
]
}
body_text es un array de arrays — Meta permite más de un juego de ejemplos. Con uno
basta.
Formato nombrado
{
"type": "BODY",
"text": "Hola {{nombre}}, tu pedido {{numero}} está confirmado.",
"example": {
"body_text_named_params": [
{ "param_name": "nombre", "example": "María" },
{ "param_name": "numero", "example": "1042" }
]
}
}
Más legible cuando la plantilla tiene muchas variables. Elige un formato y quédate con él.
Las reglas validadas
| Regla | Por qué |
|---|---|
| Toda variable necesita ejemplo | Meta usa el ejemplo para revisar la plantilla |
| El ejemplo no puede estar vacío | Rechazo automático |
| Numeración posicional sin huecos | {{1}}, {{3}} sin {{2}} es inválido |
| No mezclar posicional y nombrado | Un formato por plantilla |
| Cabecera de texto: como máximo 1 variable | Límite de Meta |
| Pie: ninguna variable | Límite de Meta |
| Botón de URL con variable exige ejemplo de URL completa | Meta valida el dominio |
Cuando algo falla:
{
"error": {
"message": "Invalid template components.",
"type": "invalid_request",
"issues": [{ "path": "components[0].text", "message": "Variable {{2}} has no example value." }]
}
}
Crear
curl -X POST https://api.joinotify.com/templates \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{
"name": "confirmacao_pedido",
"category": "UTILITY",
"language": "pt_BR",
"components": [
{ "type": "HEADER", "format": "TEXT", "text": "Pedido confirmado" },
{
"type": "BODY",
"text": "Hola {{1}}, tu pedido {{2}} está confirmado.",
"example": { "body_text": [["María", "1042"]] }
},
{ "type": "FOOTER", "text": "Joinotify" },
{
"type": "BUTTONS",
"buttons": [
{ "type": "URL", "text": "Seguir", "url": "https://ejemplo.com/pedidos/{{1}}", "example": ["https://ejemplo.com/pedidos/1042"] }
]
}
]
}'
El name solo acepta minúsculas, dígitos y guion bajo. Es la clave de envío — elígelo
pensando en cómo lo vas a llamar en el código, no en cómo lo ve el cliente.
Listar
curl 'https://api.joinotify.com/templates?status=APPROVED' \
-H 'Authorization: Bearer sk_live_xxx'
El listado viene de un espejo local, que se actualiza por webhook y se sincroniza bajo demanda cuando supera los 15 minutos.
| Campo | Para qué |
|---|---|
stale | true cuando el espejo está viejo y la sincronización falló |
syncedAt | Cuándo se actualizó el espejo por última vez |
sync | Resumen de la sincronización, si se ejecutó en esta petición |
syncError | Motivo del fallo — los datos en caché se devuelven igualmente |
refresh=true fuerza la sincronización; refresh=false sirve solo la caché. Para una
pantalla de listado, refresh=false es lo correcto: es instantáneo y el espejo ya se
mantiene solo.
Sincronizar manualmente
curl -X POST https://api.joinotify.com/templates/sync \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "all": true }'
Solo hace falta en dos casos: plantillas creadas directamente en Business Manager, o la
sospecha de que se perdió un webhook. Una sincronización ya en curso devuelve 202 y nada
se duplica.
Eliminar
curl -X DELETE 'https://api.joinotify.com/templates/confirmacao_pedido' \
-H 'Authorization: Bearer sk_live_xxx'
La eliminación afecta a todos los idiomas de ese nombre. Los mensajes ya enviados permanecen en las conversaciones de los clientes.
Después de borrarla, Meta no deja crear otra plantilla con el mismo nombre durante 30 días. Si la intención es corregir el contenido, edítala en lugar de borrarla y recrearla.
Enviar
curl -X POST https://api.joinotify.com/messages \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{
"type": "template",
"to": "5541987111527",
"name": "confirmacao_pedido",
"language": "pt_BR",
"components": [
{ "type": "header", "parameters": [{ "type": "text", "text": "Pedido confirmado" }] },
{ "type": "body", "parameters": [
{ "type": "text", "text": "María" },
{ "type": "text", "text": "1042" }
]},
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [{ "type": "text", "text": "1042" }] }
]
}'
Fíjate en la asimetría: al crear, los componentes van en mayúsculas (BODY); al
enviar, en minúsculas (body). Así es en Meta y lo reenviamos sin cambios.
En un botón de URL, el parameter es solo la parte variable — en el ejemplo anterior,
1042 completa https://ejemplo.com/pedidos/{{1}}.
Errores habituales al enviar
| Código de Meta | Qué pasó |
|---|---|
132000 | El número de parámetros no coincide con la plantilla |
132001 | La plantilla no existe en ese idioma |
132005 | Parámetro demasiado largo |
132007 | Plantilla con formato inválido |
132012 | Formato de parámetro incorrecto |
132015 | Plantilla pausada por calidad |
132016 | Plantilla deshabilitada por calidad |
Todos llegan dentro de error.meta en una respuesta 422 meta_error.