Templates

Template é a mensagem pré-aprovada pela Meta que inicia ou retoma uma conversa fora da janela de 24h. Recurso exclusivo do WhatsApp, sempre de uma conexão (WABA).

A BotoZap guarda um catálogo dos templates de cada conexão: os criados por aqui e os importados da Meta. É esse catálogo que as rotas abaixo leem. Para enviar um template (com variáveis, mídia e botões), veja Enviar mensagens — o envio usa o nome e o idioma e não depende do catálogo: um template aprovado direto no WhatsApp Manager sai mesmo antes de ser importado.

Listar templates

GET /api/v1/templates (escopo templates:read) devolve os templates da conta, mais recentes primeiro, paginados por página: page e per_page (até 100, padrão 20), com o total em meta e nos cabeçalhos X-Total, X-Total-Pages, X-Per-Page e X-Page.

GET /api/v1/templates?status=approved&category=utility&per_page=50

HTTP/1.1 200 OK
X-Total: 12
X-Total-Pages: 1
{
  "data": [
    {
      "id": "8b0f6a52-1c3e-4d7a-9e21-5f4c3b2a1d09",
      "name": "pedido_enviado",
      "language": "pt_BR",
      "category": "UTILITY",
      "status": "APPROVED",
      "rejection_reason": null,
      "meta_template_id": "1029384756473829",
      "components": [ ... ],
      "waba_connection_id": "3b9e2c1d-…",
      "created_at": "2026-09-10T12:00:00.000Z",
      "last_synced_at": "2026-09-10T12:00:03.000Z"
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total_pages": 1, "total_count": 12 }
}
FiltroComo funciona
statusStatus da Meta, sem diferenciar maiúsculas: APPROVED, PENDING, REJECTED, PAUSED… Valor desconhecido devolve lista vazia.
categoryMARKETING, UTILITY ou AUTHENTICATION, sem diferenciar maiúsculas.
waba_connection_idUUID da conexão; de outra conta, lista vazia.
phone_number_idO id do Número na Meta: lista os templates da conexão dele. Número que não é da conta, lista vazia.

Uma chave sandbox lista só o catálogo sintético do sandbox; uma de produção, só o de produção.

rejection_reason traz o motivo que a Meta informou para um template REJECTED (ex.: INVALID_FORMAT, INCORRECT_CATEGORY, TAG_CONTENT_MISMATCH) e é null nos demais status ou quando a Meta não mandou motivo. A tela de Templates do painel mostra o mesmo motivo, em português.

Consultar um template

GET /api/v1/templates/{id} recebe o UUID interno (o id da lista, não o meta_template_id) e devolve o mesmo objeto. Template de outra conta, de outro ambiente ou inexistente é 404 not_found. Vale o mesmo recorte da lista: uma chave sandbox consulta só o catálogo sintético do sandbox, e uma de produção nunca o vê.

Criar um template

POST /api/v1/templates (escopo templates:write) valida o template e o submete à análise da Meta. Informe a conexão por waba_connection_id ou pelo phone_number_id da Meta. O corpo usa o formato de criação da Meta (tipos em maiúsculas: HEADER, BODY, FOOTER, BUTTONS), diferente do formato de envio:

POST /api/v1/templates
{
  "phone_number_id": "106540352242922",
  "name": "pedido_enviado",
  "language": "pt_BR",
  "category": "UTILITY",
  "components": [
    {
      "type": "HEADER",
      "format": "IMAGE",
      "example": { "header_handle": ["4::aW1hZ2UvanBlZw==:ARb…"] }
    },
    {
      "type": "BODY",
      "text": "Oi {{1}}, seu pedido {{2}} saiu para entrega.",
      "example": { "body_text": [["Maria", "#4821"]] }
    },
    { "type": "FOOTER", "text": "Loja Exemplo" },
    {
      "type": "BUTTONS",
      "buttons": [
        {
          "type": "URL",
          "text": "Rastrear",
          "url": "https://loja.exemplo.com/rastreio/{{1}}",
          "example": ["https://loja.exemplo.com/rastreio/4821"]
        }
      ]
    }
  ]
}

A BotoZap confere antes de chamar a Meta:

  • name — letras minúsculas, números e _, até 512 caracteres (é convertido para minúsculas);
  • language — no formato pt_BR, en_US, es;
  • category — MARKETING, UTILITY ou AUTHENTICATION. A Meta pode reclassificar: a resposta traz a categoria que ela devolveu;
  • BODY obrigatório, até 1024 caracteres, com variáveis sequenciais ({{1}}, {{2}}…) e um exemplo para cada uma em example.body_text;
  • HEADER de texto até 60 caracteres e no máximo uma variável ({{1}}, com exemplo em example.header_text); cabeçalho de imagem, vídeo ou documento exige example.header_handle — o handle de POST /api/v1/media com delivery=meta_resumable_asset;
  • FOOTER até 60 caracteres, sem variável;
  • BUTTONS com 1 a 3 botões, rótulo até 25 caracteres: QUICK_REPLY, URL (com exemplo quando a URL tem variável), PHONE_NUMBER, COPY_CODE (exemplo até 15 caracteres) e CATALOG. Botões FLOW não são aceitos;
  • AUTHENTICATION tem formato próprio de OTP (e é a única categoria com botão OTP).

O que falha aqui é 422 invalid_template, com a mensagem dizendo o quê. Campos fora do formato são descartados antes de ir à Meta.

Criar um template

Submete um template à Meta. Informe a WABA com `waba_connection_id` ou `phone_number_id` — os dois vêm de `GET /api/v1/phone_numbers`.

curl https://botozap.com.br/api/v1/templates \
  -X POST \
  -H "X-API-Key: $BOTOZAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "pedido_confirmado",
  "language": "pt_BR",
  "category": "UTILITY",
  "phone_number_id": "109876543210987",
  "components": [
    {
      "type": "BODY",
      "text": "Olá {{1}}, seu pedido foi confirmado.",
      "example": {
        "body_text": [
          [
            "Maria"
          ]
        ]
      }
    }
  ]
}'

Resposta 201

{
  "data": {
    "id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "name": "pedido_confirmado",
    "language": "pt_BR",
    "category": "UTILITY",
    "status": "PENDING",
    "rejection_reason": null,
    "meta_template_id": "123456789012345",
    "components": [
      {
        "type": "BODY",
        "text": "Olá {{1}}, seu pedido foi confirmado.",
        "example": {
          "body_text": [
            [
              "Maria"
            ]
          ]
        }
      }
    ],
    "waba_connection_id": "5b0a9d0e-6e1a-4f3b-8c2d-1a9f6e0b7c11",
    "created_at": "2026-07-14T12:00:00.000Z",
    "last_synced_at": "2026-07-14T12:00:00.000Z"
  }
}

Sem `waba_connection_id` e sem `phone_number_id`: 422 `missing_connection`. Conexão ou número que não é da conta: 404 `not_found`.

`name` só aceita minúsculas, números e underscore (`pedido_confirmado`). `language` no formato `pt_BR`. `category`: `UTILITY`, `MARKETING` ou `AUTHENTICATION`.

O corpo é obrigatório. Variáveis precisam ser sequenciais (`{{1}}`, `{{2}}`) e cada uma leva um valor em `example.body_text`. Payload local inválido é 422 `invalid_template` — a Meta não é chamada.

Em live o 201 volta com `status: PENDING`; só envie o template quando a listagem mostrar `APPROVED`. A Graph recusando o conteúdo é 422 `meta_error`; 5xx da Graph é 502 `meta_error`.

Chave sandbox cria o template já `APPROVED`, sem chamar a Meta. Use a conexão/número sandbox da conta — misturar live e sandbox é 404.

Nome e idioma que já existem no catálogo da conexão: 409 `duplicate`, em live e no sandbox, antes de chamar a Meta. O template existente não é alterado.

Erros ao criar

HTTPcodeQuando
422missing_connectionSem waba_connection_id nem phone_number_id.
404not_foundConexão ou número que não é da conta, conexão inativa ou de outro ambiente (sandbox × produção); no sandbox, também quando o ambiente sintético ainda não foi criado.
422invalid_templateFalhou numa das regras acima.
503plan_unavailableProdução: não foi possível ler o plano da conta. Nada foi enviado à Meta; repita com backoff.
409not_connectedA conexão não tem token de acesso: refaça a conexão.
422 / 502meta_errorA Meta recusou (422) ou falhou (502). Um nome que já existe na WABA mas ainda não foi importado para o catálogo chega aqui como recusa da Meta: sincronize os templates no painel e ele passa a responder duplicate.
502create_failedFalha inesperada na chamada à Meta.
409duplicateNome e idioma já existem no catálogo da conexão (sandbox ou produção). Nada é alterado e a Meta não é chamada.
502persist_failedA Meta recebeu o template, mas o catálogo não gravou. Não crie de novo (a Meta recusaria o nome repetido): sincronize os templates no painel.

Sem conexão informada, a API responde antes de olhar o resto do corpo:

Erro: criar template sem conexão

O POST de template não adivinha a WABA. Sem `waba_connection_id` e sem `phone_number_id`, a API recusa antes de validar o restante do payload.

curl https://botozap.com.br/api/v1/templates \
  -X POST \
  -H "X-API-Key: $BOTOZAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "pedido_confirmado",
  "language": "pt_BR",
  "category": "UTILITY",
  "components": [
    {
      "type": "BODY",
      "text": "Olá, seu pedido foi confirmado."
    }
  ]
}'

Resposta 422

{
  "error": {
    "code": "missing_connection",
    "message": "Informe 'waba_connection_id' ou 'phone_number_id' para criar o template."
  }
}

Pegue os dois ids em `GET /api/v1/phone_numbers`: `waba_connection_id` é o UUID da conexão; `phone_number_id` é o id Meta do número. Basta um dos dois.

Aprovação: de PENDING a APPROVED

A resposta do POST traz o status que a Meta devolveu na criação — em geral PENDING. A análise da Meta leva de minutos a horas e termina em APPROVED ou REJECTED; depois, a Meta ainda pode mudar o status (ex.: PAUSED por qualidade).

A BotoZap recebe essas mudanças da Meta e atualiza o status do catálogo, mas não dispara um evento para o seu webhook. Para saber quando o template ficou pronto, consulte GET /api/v1/templates/{id} (ou a lista com status=pending) de tempos em tempos. Numa rejeição, rejection_reason traz o motivo informado pela Meta. Enviar um template ainda PENDING é recusado pela Meta (422 meta_error).

Sandbox

Com chave bz_sandbox_, POST /api/v1/templates grava no catálogo sintético do sandbox sem chamar a Meta, e o template já nasce APPROVED (com last_synced_at: null). Criar de novo o mesmo nome e idioma responde 409 duplicate e mantém o existente, como em produção. O envio pelo sandbox aceita qualquer nome, esteja ou não no catálogo — o catálogo serve para você testar a listagem e o seu fluxo de criação.

O que só o painel faz

  • Editar um template (a edição volta para análise da Meta; nome e idioma não mudam);
  • Excluir um template;
  • Sincronizar o catálogo de um Cliente com a Meta — é o que importa templates criados direto no WhatsApp Manager, remove os que foram excluídos lá, grava o rejection_reason e corrige um persist_failed. Até lá, essas mudanças não aparecem em GET /api/v1/templates.

No painel, fica em Campanhas › Templates.