Templates
Template é uma mensagem aprovada previamente pela Meta. É a única forma de escrever para alguém fora da janela de 24 horas.
Templates pertencem à WABA (conta empresarial), não a um número — todos os números da mesma conta compartilham a mesma biblioteca.
Categorias
| Categoria | Para quê | Custo |
|---|---|---|
UTILITY | Confirmação, atualização de pedido, lembrete de agendamento | baixo |
AUTHENTICATION | Código de verificação | baixo |
MARKETING | Promoção, novidade, reengajamento | mais alto |
A Meta reclassifica templates que ela julga mal categorizados. Um "utilitário" que na
prática vende vira MARKETING, e o custo sobe. allowCategoryChange: true (o padrão)
deixa isso acontecer em vez de rejeitar o template.
Ciclo de vida
A aprovação leva de alguns minutos a algumas horas. O resultado chega pelo webhook
message_template_status_update — não fique consultando em loop.
PAUSED acontece quando muitos clientes bloqueiam ou denunciam mensagens daquele
template. Ele volta sozinho se a qualidade se recuperar; se piorar, vira DISABLED e não
envia mais.
Variáveis e exemplos
Todo template com variáveis precisa declarar exemplos — sem eles a Meta rejeita. O
Joinotify valida isso antes de enviar e devolve 422 com o campo issues apontando o
problema, o que evita o ciclo lento de submeter, esperar e ser rejeitado.
Formato posicional
{
"name": "confirmacao_pedido",
"category": "UTILITY",
"language": "pt_BR",
"components": [
{
"type": "BODY",
"text": "Olá {{1}}, seu pedido {{2}} foi confirmado e chega em {{3}}.",
"example": { "body_text": [["Maria", "1042", "2 dias úteis"]] }
}
]
}
body_text é um array de arrays — a Meta permite mais de um conjunto de exemplos.
Um só já basta.
Formato nomeado
{
"type": "BODY",
"text": "Olá {{nome}}, seu pedido {{numero}} foi confirmado.",
"example": {
"body_text_named_params": [
{ "param_name": "nome", "example": "Maria" },
{ "param_name": "numero", "example": "1042" }
]
}
}
Mais legível quando o template tem muitas variáveis. Escolha um formato e fique nele.
As regras validadas
| Regra | Por quê |
|---|---|
| Toda variável precisa de exemplo | A Meta usa o exemplo para revisar o template |
| Exemplo não pode ser vazio | Vira rejeição automática |
| Numeração posicional sem lacunas | {{1}}, {{3}} sem {{2}} é inválido |
| Não misturar posicional e nomeado | Mesmo template, um formato só |
| Header de texto: no máximo 1 variável | Limite da Meta |
| Footer: nenhuma variável | Limite da Meta |
| Botão de URL com variável exige exemplo de URL completa | A Meta valida o domínio |
Quando algo falha:
{
"error": {
"message": "Invalid template components.",
"type": "invalid_request",
"issues": [{ "path": "components[0].text", "message": "Variable {{2}} has no example value." }]
}
}
Criando
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": "Olá {{1}}, seu pedido {{2}} foi confirmado.",
"example": { "body_text": [["Maria", "1042"]] }
},
{ "type": "FOOTER", "text": "Joinotify" },
{
"type": "BUTTONS",
"buttons": [
{ "type": "URL", "text": "Acompanhar", "url": "https://exemplo.com/pedidos/{{1}}", "example": ["https://exemplo.com/pedidos/1042"] }
]
}
]
}'
O name aceita só minúsculas, números e underscore. Ele é a chave de envio — escolha
pensando em como vai chamá-lo no código, não em como aparece para o cliente.
Listando
curl 'https://api.joinotify.com/templates?status=APPROVED' \
-H 'Authorization: Bearer sk_live_xxx'
A listagem vem de um espelho local, que é atualizado por webhook e sincronizado sob demanda quando fica mais velho que 15 minutos.
| Campo | Para quê |
|---|---|
stale | true quando o espelho está velho e a sincronização falhou |
syncedAt | Quando o espelho foi atualizado pela última vez |
sync | Resumo da sincronização, quando uma rodou nesta requisição |
syncError | Motivo da falha — os dados em cache ainda vêm na resposta |
refresh=true força a sincronização; refresh=false serve só o cache. Para uma tela de
listagem, refresh=false é o certo: é instantâneo e o espelho já se mantém sozinho.
Sincronizando manualmente
curl -X POST https://api.joinotify.com/templates/sync \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "all": true }'
Só é necessário em dois casos: templates criados direto no Business Manager, ou suspeita
de que um webhook se perdeu. Uma sincronização já em andamento devolve 202 e nada é
duplicado.
Removendo
curl -X DELETE 'https://api.joinotify.com/templates/confirmacao_pedido' \
-H 'Authorization: Bearer sk_live_xxx'
A remoção vale para todos os idiomas daquele nome. Mensagens já enviadas continuam nas conversas dos clientes.
Depois de apagar, a Meta não deixa criar outro template com o mesmo nome por 30 dias. Se a intenção é corrigir o conteúdo, edite em vez de apagar e recriar.
Enviando
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": "Maria" },
{ "type": "text", "text": "1042" }
]},
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [{ "type": "text", "text": "1042" }] }
]
}'
Note a assimetria: na criação os componentes vão em maiúsculas (BODY), no envio
em minúsculas (body). É assim na Meta e nós repassamos sem alterar.
Em botão de URL, o parameter é apenas a parte variável — no exemplo acima, 1042
completa https://exemplo.com/pedidos/{{1}}.
Erros comuns no envio
| Código da Meta | O que houve |
|---|---|
132000 | Número de parâmetros não bate com o template |
132001 | Template não existe nesse idioma |
132005 | Parâmetro longo demais |
132007 | Template com formato inválido |
132012 | Formato de parâmetro incorreto |
132015 | Template pausado por qualidade |
132016 | Template desabilitado por qualidade |
Todos chegam dentro de error.meta numa resposta 422 meta_error.