Pular para o conteúdo principal

WABA Templates

Templates aprovados pela Meta para envio de mensagens proativas (marketing, utility, authentication). Sem template, você só pode mandar mensagem dentro da janela de 24h após uma inbound do usuário.

Base path: /v1/templates

Apenas WABA. Baileys não tem conceito de template.


Lifecycle

POST /v1/templates


DRAFT ────► (gateway envia pra Meta sync)


PENDING ──► (Meta revisa, ~minutos a ~24h)

├──► APPROVED ──► usável em POST /v1/messages type=template
├──► REJECTED ──► verifique rejectionReason
└──► PAUSED ──► (Meta pausou por baixo engagement)

Após APPROVED, você pode editar (volta pra PENDING).


Componentes suportados

Cada template é composto por componentes (HEADER, BODY, FOOTER, BUTTONS, CAROUSEL, LIMITED_TIME_OFFER). O schema completo:

{ "type": "HEADER", "format": "TEXT", "text": "Olá {{1}}", "example": { "header_text": ["João"] } }

Formats: TEXT, IMAGE, VIDEO, DOCUMENT, LOCATION.

Para mídia, use example com header_handle (de POST /v1/media/upload/resumable).

BODY

{ "type": "BODY", "text": "Sua consulta {{1}} foi confirmada para {{2}}", "example": { "body_text": [["Cardiologia", "20/05 às 14h"]] } }

Body é obrigatório. Suporta variáveis {{1}}, {{2}}, ... que viram variables em POST /v1/messages.

{ "type": "FOOTER", "text": "Acme Saúde" }

Máx 60 chars, sem variáveis.

BUTTONS

Lista de até 10 botões. Tipos suportados:

TipoDescrição
QUICK_REPLYBotão de resposta rápida (texto curto)
URLAbre URL (suporta variáveis em URL: https://acme.com/{{1}})
PHONE_NUMBERDisca número
COPY_CODECopia código (cupom, PIX, etc)
OTPAuthentication template (COPY_CODE/ONE_TAP/ZERO_TAP)
CATALOGAbre catálogo da WABA
MPMMulti-Product Message
SPMSingle-Product Message
FLOWAbre WhatsApp Flow
VOICE_CALLInicia voice call WABA

Exemplo URL com variável:

{
"type": "BUTTONS",
"buttons": [
{ "type": "URL", "text": "Ver pedido", "url": "https://acme.com/pedido/{{1}}", "example": ["12345"] }
]
}

Marketing template com até 10 cards (cada card = mini-template com HEADER mídia + BODY + BUTTONS).

{
"type": "CAROUSEL",
"cards": [
{
"components": [
{ "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["h:..."] } },
{ "type": "BODY", "text": "Produto A — R$ {{1}}", "example": { "body_text": [["99,90"]] } },
{ "type": "BUTTONS", "buttons": [{ "type": "URL", "text": "Comprar", "url": "https://acme.com/a" }] }
]
}
]
}

LIMITED_TIME_OFFER

Template com countdown:

{
"type": "LIMITED_TIME_OFFER",
"limited_time_offer": { "text": "Oferta termina em", "has_expiration": true }
}

POST /v1/templates

Cria template e submete à Meta de forma síncrona (a request espera a chamada Graph API).

Body

CampoTipoObrigatórioDescrição
phoneIdUUIDsimPhone WABA dono
namestringsim1-120, [a-z0-9_]+ (lowercase + underscore)
languagestringsimpt_BR, en_US, etc.
categoryenumsimMARKETING, UTILITY, AUTHENTICATION
componentsarraysim1+ componentes
allowCategoryChangebooleannãoDefault true. Meta pode reclassificar
messageSendTtlSecondsintnão30-43200 (12h). Auto-fail se não entregue
curl -X POST https://wpp.ogmma.com.br/v1/templates \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"phoneId": "5f7b2e1c-...",
"name": "boas_vindas_v2",
"language": "pt_BR",
"category": "UTILITY",
"components": [
{ "type": "BODY", "text": "Olá {{1}}, bem-vindo!", "example": { "body_text": [["João"]] } },
{ "type": "FOOTER", "text": "Acme Saúde" },
{
"type": "BUTTONS",
"buttons": [{ "type": "QUICK_REPLY", "text": "Saiba mais" }]
}
]
}'

Response 201

{
"id": "tpl-1234-...",
"accountId": "...",
"phoneId": "5f7b2e1c-...",
"name": "boas_vindas_v2",
"language": "pt_BR",
"category": "UTILITY",
"status": "PENDING",
"components": [ ... ],
"metaTemplateId": "987654321",
"createdAt": "2026-05-21T14:00:00.000Z"
}

Errors

HTTPCodeQuando
400VALIDATION_ERRORSchema inválido
400WABA_CREDS_INCOMPLETEPhone sem wabaPhoneNumberId/accessToken
404PHONE_NOT_FOUNDPhone não é WABA ou não pertence à account
409ALREADY_EXISTSJá existe template aprovado/submetido com o mesmo (phoneId, name, language)

Quando a Meta recusa na hora (variável sem exemplo, ratio, nome repetido), a linha local fica em REJECTED sem metaTemplateId. Reenviar com o mesmo nome depois de corrigir funciona: o gateway reaproveita a mesma linha (mesmo id, status volta para DRAFT/PENDING) em vez de responder 409. O 409 ALREADY_EXISTS fica só para template que existe de verdade na Meta — ou para uma submissão do mesmo nome ainda em voo (linha em DRAFT), caso em que basta repetir depois da primeira terminar.

O corpo do erro de recusa carrega details.metaError inteiro — code, error_subcode (ex.: 2388293 variáveis demais, 2388299 não pode começar/terminar com variável, 2388024 nome já existe) e error_user_msg — para o cliente traduzir por código em vez de por substring.


GET /v1/templates

Lista templates da account.

curl https://wpp.ogmma.com.br/v1/templates \
-H "X-API-Key: ak_live_..."

Response 200 — array.


GET /v1/templates/:id

Detalhe.

curl https://wpp.ogmma.com.br/v1/templates/tpl-1234-... \
-H "X-API-Key: ak_live_..."

Errors

HTTPCodeQuando
404TEMPLATE_NOT_FOUND

PATCH /v1/templates/:id

Edita um template já sincronizado com Meta. Mantém name/language; permite mudar components, category, messageSendTtlSeconds. Após edit, Meta re-revisa (status volta para PENDING).

Body (todos opcionais)

CampoTipoObrigatório se quiser editar
componentsarray
categoryenum
messageSendTtlSecondsint
curl -X PATCH https://wpp.ogmma.com.br/v1/templates/tpl-1234-... \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"components": [
{ "type": "BODY", "text": "Versão atualizada {{1}}", "example": { "body_text": [["X"]] } }
]
}'

Template REJECTED sem metaTemplateId (a Meta recusou na criação): o PATCH re-submete como criação, reaproveitando a mesma linha — não há o que editar na Meta. components e category omitidos mantêm os valores atuais da linha, e messageSendTtlSeconds é recusado com 400 nesse caminho (a criação não repassa TTL). Template em DRAFT — submissão ainda em voo — continua respondendo 409 TEMPLATE_NOT_SUBMITTED.

Errors

HTTPCodeQuando
400VALIDATION_ERRORBody inválido; messageSendTtlSeconds na re-submissão; ou components gravados que não validam no schema atual
400META_BAD_REQUESTMeta recusou a edição ou a re-submissão (details.metaError)
404TEMPLATE_NOT_FOUNDTemplate não pertence à account
409TEMPLATE_NOT_SUBMITTEDTemplate em DRAFT (submissão em voo)
500WABA_CREDS_MISSINGPhone sem credenciais WABA

DELETE /v1/templates/:id

Deleta na Meta + remove local. Se Meta falhar (ex.: template já em uso), remove localmente mesmo assim (log de warn).

curl -X DELETE https://wpp.ogmma.com.br/v1/templates/tpl-1234-... \
-H "X-API-Key: ak_live_..."

Response 204


POST /v1/templates/sync

Re-importa todos os templates de uma WABA direto da Meta (útil quando o time criou templates pelo Business Manager).

Body

CampoTipoObrigatório
phoneIdUUIDsim
curl -X POST https://wpp.ogmma.com.br/v1/templates/sync \
-H "X-API-Key: ak_live_..." \
-H "Content-Type: application/json" \
-d '{ "phoneId": "5f7b2e1c-..." }'

Response 200 — objeto com counts de criados/atualizados.


Notificação de aprovação

Quando a Meta aprova/rejeita um template, o webhook template:status é publicado (assumindo que você subscreve esse evento). O DB local é atualizado automaticamente pelo handler meta-webhook. Você pode também fazer polling em GET /v1/templates/:id.


Boas práticas

  1. Nomes únicos por phone+language: boas_vindas_v2 + pt_BR é único — versione o nome se mudar muito o body.
  2. Categorias corretas: MARKETING é cobrado mais caro que UTILITY. Meta auto-reclassifica se identificar abuse.
  3. Examples obrigatórios: Meta exige example em componentes com variáveis para entender o uso real.
  4. Header media via handle: para template com IMAGE/VIDEO header, não use url — suba via POST /v1/media/upload/resumable e use o handle no example.header_handle.