CRPRO HubEntrar no painel

Como enviar um template de WhatsApp pela API

Template é o único jeito de falar com quem não falou com você primeiro. O endpoint é o mesmo do texto livre — muda o type e o objeto que o acompanha. A parte que dá trabalho não é a requisição: é entender por que a Meta recusa um template que ela mesma aprovou.

Quando o template é obrigatório

A janela de atendimento abre quando o contato manda uma mensagem para o seu número e dura 24 horas. Dentro dela você envia o que quiser — texto, mídia, botão. Fora dela, só sai template aprovado. Não é regra do CRPRO Hub: é da Meta, e a recusa acontece do lado dela.

Na prática isso significa que qualquer coisa iniciada por você — cobrança, confirmação de pedido, lembrete de consulta, código de acesso — precisa de template, porque nesses casos não existe janela aberta. Se a sua aplicação não sabe quando a janela abriu, ela também não sabe se pode mandar texto livre; nesse caso, mandar template sempre é a escolha previsível, ainda que mais cara.

Não existe endpoint que responda “a janela deste contato está aberta?”. Quem precisa dessa informação a deriva do próprio histórico: o evento message.received do webhook marca o instante em que a janela abriu. Veja receber mensagens.

Listar os templates aprovados

O nome e o idioma que você vai mandar no envio precisam existir na Meta, aprovados, para aquele canal. A listagem é a fonte:

Requisição
curl https://crprohub.com/api/v1/channels/CHANNEL_UUID/templates \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL"
Resposta
{
  "data": {
    "templates": [
      {
        "id": "9876543210987654",
        "name": "boas_vindas",
        "language": "pt_BR",
        "category": "UTILITY",
        "status": "APPROVED"
      }
    ],
    "paging": null
  }
}

Envie apenas o que estiver com status igual a APPROVED. Um template em PENDING ainda está em revisão e a Meta recusa o envio. O escopo desta chamada é templates:read — diferente do messages:send que o envio exige, então uma chave que só lista não consegue enviar e vice-versa.

A resposta é a lista da Meta repassada, com cache de até 60 segundos por WABA. Criar ou excluir template pelo Hub limpa esse cache na hora, mas uma aprovação que aconteceu no Business Manager pode levar até um minuto para aparecer aqui. Se o canal ainda não tem WABA conectada, a chamada responde 409 CHANNEL_NOT_CONNECTED em vez de uma lista vazia — a diferença importa, porque lista vazia parece “nenhum template cadastrado” e não é isso que está acontecendo.

A requisição

Mesmo endpoint do envio comum, mesmo Idempotency-Key obrigatório. O que muda é type: "template" e o objeto template:

Requisição
curl -X POST https://crprohub.com/api/v1/channels/CHANNEL_UUID/messages \
  -H "Authorization: Bearer hub_pk_EXEMPLO_NAO_REAL" \
  -H "Idempotency-Key: 7f1c0e2a-58b3-4a91-9c4d-2e6f8b0a1d33" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5521999999999",
    "type": "template",
    "template": {
      "name": "pedido_a_caminho",
      "language": { "code": "pt_BR" },
      "components": [
        {
          "type": "body",
          "parameters": [
            { "type": "text", "text": "Ana" },
            { "type": "text", "text": "BR123456789" }
          ]
        }
      ]
    }
  }'
Resposta
{
  "data": {
    "message_id": "wamid.EXEMPLO123",
    "status": "accepted"
  }
}

O 202 quer dizer aceito e enfileirado, não entregue — igual ao envio de texto. A recusa de um template pela Meta é assíncrona na maioria dos casos: ela chega depois, no evento message.status do webhook, não nesta resposta. Detalhes em enviar mensagem.

Dentro de template, name aceita só letras minúsculas, dígitos e underscore, e language é um objeto no formato { "code": "pt_BR" } — não a string "pt_BR" que a listagem devolve. Copiar o campo da listagem direto para o envio é o erro de digitação mais comum aqui. Template sem variável nenhuma dispensa components:

Template sem variáveis
{
  "to": "5521999999999",
  "type": "template",
  "template": {
    "name": "aviso_de_manutencao",
    "language": { "code": "pt_BR" }
  }
}

Como preencher as variáveis

components aceita até 20 itens, e o type de cada um é header, body ou button — minúsculo. Na criação de template os componentes são HEADER, BODY, FOOTER e BUTTONS, em maiúsculo; no envio não. São dois contratos diferentes da Meta, e o corpo é validado de forma estrita: componente com nome em maiúsculo, ou footer — que não existe no envio, porque rodapé não tem variável — derruba a requisição inteira.

Componentes
"components": [
  {
    "type": "header",
    "parameters": [
      { "type": "image", "image": { "id": "1234567890123456" } }
    ]
  },
  {
    "type": "body",
    "parameters": [
      { "type": "text", "text": "Ana" },
      { "type": "currency", "currency": { "fallback_value": "R$ 89,90", "code": "BRL", "amount_1000": 89900 } },
      { "type": "date_time", "date_time": { "fallback_value": "12 de março" } }
    ]
  },
  {
    "type": "button",
    "sub_type": "url",
    "index": "0",
    "parameters": [
      { "type": "text", "text": "pedido/BR123456789" }
    ]
  }
]

Cada componente leva até 100 parameters, na ordem em que aparecem no template aprovado: a primeira posição da lista preenche {{1}}, a segunda {{2}} e assim por diante. Não há nome ligando parâmetro a variável, então trocar dois valores de lugar não gera erro nenhum — gera uma mensagem correta com o conteúdo errado no cliente.

  • text — { "type": "text", "text": "Ana" }, até 4096 caracteres.
  • currency e date_time — trazem um fallback_value, que é o texto exibido quando a Meta não consegue formatar o valor para o idioma do aparelho. Em currency, amount_1000 é o valor multiplicado por mil: R$ 89,90 vira 89900.
  • image, video e document — para cabeçalho de mídia. Aceitam id (o media ID devolvido pelo upload) ou link (URL https), um dos dois, nunca os dois juntos. Veja enviar mídia.
  • payload — o dado que volta quando o cliente toca em um botão de resposta rápida, até 1000 caracteres.

Componente de botão precisa de sub_type — quick_reply, url, catalog ou mpm — e de index, a posição do botão no template aprovado, de 0 a 99. Um index apontando para um botão que não existe é recusado pela Meta, não pela validação local.

Aprovado, mas recusado no envio

A validação do Hub confere a forma do corpo, não o conteúdo: ela não sabe quantas variáveis o seu template tem, nem em que categoria ele foi aprovado. Um corpo bem formado passa e vira 202. A recusa, quando vem, vem da Meta — como 502 META_SEND_FAILED na hora, ou como um message.status de falha depois. Em ambos os casos o meta_code nos detalhes do erro é o que diz o motivo real. Os casos que mais aparecem:

  • Contagem de variáveis errada. O template aprovado tem três {{n}} no corpo e você mandou dois parâmetros — ou quatro. Tem que bater exatamente, e a conferência só acontece na Meta. Variável a menos é o motivo mais frequente de recusa em produção.
  • Categoria diferente da que você supõe. A Meta reclassifica templates durante a revisão — algo criado como UTILITY pode ter sido aprovado como MARKETING. Isso muda o preço e sujeita o envio ao limite de marketing por número e ao opt-out do contato. Confira o campo category na listagem, não a intenção de quem criou.
  • Idioma sem versão aprovada. Nome e idioma são um par: boas_vindas aprovado em pt_BR não existe em en. A listagem devolve uma linha por par.
  • Qualidade do número baixa. A Meta reduz o limite diário de contatos iniciados e chega a bloquear categorias inteiras quando o número acumula bloqueios e denúncias. O template continua aprovado; o que caiu foi a permissão de usá-lo naquele volume. A recuperação é gradual e não tem atalho pela API.
  • Template aprovado em outra WABA. Aprovação pertence à conta WhatsApp Business, não à sua organização no Hub. Um canal novo, de outra WABA, não herda os templates do anterior.
Não trate 502 como erro transitório e repita a chamada em laço. Se a causa é contagem de variável ou categoria, reenviar produz exatamente o mesmo resultado e só consome o limite de 60 envios por minuto por canal. Corrija o corpo e mande com uma Idempotency-Key nova — corpo diferente com a chave antiga responde 409 IDEMPOTENCY_KEY_REUSED.

O que muda em outubro de 2026

Até 30 de setembro de 2026, o template de utilidade enviado dentro da janela de atendimento não é cobrado. A partir de 1º de outubro de 2026 ele passa a ser, desde o primeiro. A resposta sem template dentro da janela também passa a ser cobrada, mas só depois das 1.000 gratuitas do mês em cada número — e essa franquia não vale para template. Quem manda template de utilidade por conveniência — porque é mais simples do que controlar a janela — paga por cada um desde essa data.

A Meta também pediu meio de pagamento cadastrado na conta WhatsApp Business até 30 de setembro de 2026. Sem ele, a Meta entrega as respostas sem template só até acabar a franquia mensal do número, e as seguintes deixam de sair. Como a conta é do cliente final, é ele quem cadastra. O que muda, o que não muda e o que conferir estão em mudança na cobrança do WhatsApp em outubro de 2026.

Quando falha

  • 400 INVALID_MESSAGE — o objeto template não bate com o schema: language como string, componente em maiúsculo, campo extra, mídia com id e link ao mesmo tempo.
  • 401 — chave errada, revogada, ou o valor enviado sem o prefixo Bearer.
  • 403 — a chave não tem messages:send para enviar, ou templates:read para listar.
  • 404 — o canal não existe ou pertence a outra organização. A API responde 404 nos dois casos de propósito: 403 revelaria que o recurso existe.
  • 409 CHANNEL_NOT_CONNECTED — o canal não tem WABA associada. Conclua a conexão antes de listar ou enviar.
  • 409 IDEMPOTENCY_KEY_REUSED — a mesma chave com um corpo diferente.
  • 429 — limite de taxa: 60 envios por minuto por canal, além do limite geral da organização. Veja limites.
  • 502 META_SEND_FAILED — a Meta recusou. Leia o meta_code em details antes de qualquer retentativa.

Toda resposta traz x-request-id; guarde-o para cruzar com /painel/logs. A tabela completa está em erros, e o contrato dos endpoints em referência de templates.

O token do canal (hub_ch_...) autentica tanto a listagem quanto o envio, como alternativa à chave de API. Ele vale só para o canal que o emitiu e nunca autoriza ler templates nem enviar por outro canal da organização — é a credencial certa para entregar a um sistema que cuida de um cliente só.