Pular para o conteúdo principal

API oficial da Meta

O bZapper tem dois trilhos. No não oficial, um número é uma sessão pareada por QR code. No oficial, as mensagens saem pela WhatsApp Cloud API, com o número que pertence à conta comercial do seu cliente na Meta.

Você escolhe o trilho na criação do projeto, num botão. Depois disso, a promessa é que nada muda: POST /messages/text é POST /messages/text, o SDK é o mesmo, os webhooks são os mesmos, a inbox é a mesma.

Esta página é sobre o que só existe quando a Meta é a transportadora.


O que muda, em uma tabela​

Trilho não oficialTrilho oficial
Como o número entraQR code no painelUma janela da Meta, uma vez
Iniciar conversa friaLivre (com risco de bloqueio)Só por template aprovado
Botões e listasCaem em menu de textoNativos (dentro dos limites da Meta)
EnqueteSimNão existe na Cloud API
GruposSimNão existem na Cloud API
CampanhasTexto com variações, ramp-up de 7 diasTemplate aprovado, sem ramp-up
AgendamentoSimSim — a janela de 24h é avaliada na hora do envio
Quem cobra a conversaNinguémA Meta, no cartão do seu cliente
Risco de banRealPraticamente nulo

O que não existe num trilho não fica fingindo que existe: num projeto oficial, as rotas do número por QR (criar e parear instância, grupos, presença, editar ou apagar mensagem, bloqueio, etiquetas, chamadas) respondem 409 official_rail_unsupported; num projeto de QR, /official/* e /templates respondem 409 official_project_required. A matriz completa, rota a rota, está no campo x-rails da referência da API.

A cobrança merece uma frase a mais: a Meta cobra as conversas diretamente do seu cliente, no cartão que ele cadastra na conta comercial dele. O bZapper não intermedeia nem coloca margem nesse valor. Sua assinatura do bZapper é uma coisa; a tarifa por conversa da Meta é outra.


Conectar a conta​

Uma vez, no painel, em Números. O cliente entra com a conta do Facebook dele, escolhe (ou cria) a conta comercial, informa o número, confirma o código e autoriza. É o único momento em que ele sai do bZapper.

Depois disso falta um passo que a Meta não deixa fazer por API: anexar um cartão à conta comercial. Sem ele, ela recusa os envios. O painel explica com o link exato, e quando o cartão entra nós detectamos sozinhos — o passo se fecha sem ninguém clicar em nada.

Pela API, GET /official/status conta a mesma história, ao vivo:

{ "connected": true, "can_send": true, "display_number": "+55 11 3333-3333",
"number_verified": true, "name_status": "APPROVED", "business_review": "APPROVED",
"quality_rating": "GREEN", "messaging_limit": "TIER_1K", "live": true }

can_send é a única pergunta que interessa. live: false quer dizer que não conseguimos falar com a Meta agora e os valores são os últimos conhecidos — a diferença entre "está assim" e "não conseguimos verificar" importa quando você automatiza em cima disso.

Vários números no mesmo projeto​

A Meta permite vários números na mesma conta comercial, e o bZapper acompanha: GET /official/numbers lista todos. Cada um tem um instance_id — o mesmo valor que você informa no envio para escolher por qual número a mensagem sai, exatamente como no trilho não oficial.

Sem instance_id, vale a afinidade de atendimento: sai pelo número que já conversa com aquele contato. Sem conversa anterior, sai pelo número padrão do projeto.

Para conectar mais um número da mesma conta comercial, não é preciso autorizar de novo — a autorização é da conta, não do número. GET /official/numbers/available lista os números que a conta tem na Meta e ainda não estão no projeto; POST /official/numbers com o phone_number_id escolhido conecta. Conferimos na Meta que o número é mesmo daquela conta antes de gravar, então um id errado falha ali, e não no primeiro envio.

curl -X POST https://api.bzapper.com.br/official/numbers \
-H "Authorization: Bearer $BZAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{ "phone_number_id": "106540352242922" }'

PUT /official/numbers/{id}/default troca o número padrão, e DELETE /official/numbers/{id} desconecta um número — o histórico de conversas dele fica.


A janela de 24 horas​

Esta é a regra que define o trilho oficial, e vale a pena entendê-la de uma vez.

Você pode mandar texto livre para alguém durante 24 horas contadas da última mensagem que essa pessoa te mandou. Fora dessa janela, a Meta recusa qualquer texto livre. Só passa um template aprovado por ela.

Não é uma limitação do bZapper e não há como contorná-la: é o desenho da Cloud API. O que o bZapper faz é impedir que ela te pegue de surpresa.

O mesmo endpoint funciona dos dois lados​

Configure um template de alternativa no projeto e pronto:

# uma vez
curl -X PUT https://api.bzapper.com.br/templates/fallback \
-H "Authorization: Bearer $BZAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{"template_id":"..."}'

A partir daí, o mesmo POST /messages/text funciona dentro e fora da janela. Quando ela está fechada, a mensagem sai pelo template — e a resposta conta o que aconteceu:

{
"message_id": "0f2c…",
"status": "queued",
"delivery": {
"via": "template_fallback",
"template": "reengajamento",
"note": "A janela de 24 horas com este contato estava fechada: a mensagem saiu pelo template de alternativa configurado no projeto."
}
}

O campo delivery só aparece quando houve desvio. A plataforma decide por você, mas não esconde o que decidiu — e a note é uma frase pronta para mostrar a uma pessoa.

Sem alternativa configurada, o envio fora da janela falha com 422 official_needs_template, dizendo o que configurar. Preferimos isso a entregar de volta o 131047 da Meta, que é o que você teria sem nós.


Templates​

Um template é um modelo de mensagem aprovado pela Meta. A aprovação é assíncrona e pode levar até 24 horas; a Meta reprova com um motivo lacônico e limita quantos você pode submeter.

Por isso o bZapper valida antes de enviar — e é aqui que este trilho se paga.

Criar​

Você descreve a mensagem numa forma simples. Nós montamos o formato de componentes que a Meta espera, com os exemplos aninhados em três níveis e as regras que ela não documenta junto.

curl -X POST https://api.bzapper.com.br/templates \
-H "Authorization: Bearer $BZAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "pedido_confirmado",
"language": "pt_BR",
"category": "UTILITY",
"header": { "type": "text", "text": "Pedido {{1}}", "example": "10432" },
"body": {
"text": "Olá {{1}}, seu pedido {{2}} foi confirmado e já está em separação.",
"example": ["Ana", "10432"]
},
"footer": "Loja do Berni",
"buttons": [{ "type": "quick_reply", "text": "Acompanhar" }]
}'

Cabeçalho de imagem, vídeo ou documento também é analisado pela Meta, a partir de um arquivo de amostra. Você não precisa hospedá-lo em lugar nenhum: suba com POST /templates/media (multipart, campo file — JPEG, PNG, MP4 ou PDF até 10 MB) e use a url devolvida em header.media_url. É só a amostra que a Meta analisa, não a mídia que vai em cada envio.

curl -X POST https://api.bzapper.com.br/templates/media \
-H "Authorization: Bearer $BZAPPER_KEY" \
-F "[email protected]"
# { "url": "https://…/banner.jpg" } → "header": { "type": "image", "media_url": "https://…/banner.jpg" }

As regras que reprovam (e que barramos antes)​

Cada uma destas volta como 422 com uma frase que diz o que consertar, em vez de virar uma reprovação da Meta 24 horas depois:

  • o corpo não pode começar com {{1}} — precisa de texto antes;
  • o corpo não pode terminar com variável — precisa de texto depois;
  • duas variáveis coladas ({{1}} {{2}}) não passam;
  • a numeração é {{1}}, {{2}}, {{3}} — sem pular e começando em 1;
  • toda variável precisa de um exemplo (a reprovação mais comum de todas);
  • o rodapé não aceita variável, e tem 60 caracteres;
  • o cabeçalho de texto aceita uma variável, e tem 60 caracteres;
  • no máximo 10 botões, sendo até 2 de link e 1 de telefone.

A categoria decide o preço​

MARKETING, UTILITY ou AUTHENTICATION não é rótulo administrativo: é o que define quanto a conversa custa e o que a Meta aceita no conteúdo.

UTILITY é mais barata, mas só cobre o que o cliente já espera receber — confirmação, atualização de pedido, lembrete. Se você escrever "aproveite nosso desconto" num template UTILITY, a Meta reclassifica sozinha para MARKETING, e o preço muda sem você perceber.

A resposta da criação traz um campo advice justamente com esse tipo de aviso: coisas que não impediram a submissão, mas mudam o resultado.

Acompanhar a decisão​

Você não precisa ficar consultando. A decisão chega por webhook:

EventoQuando
template.approvedA Meta aprovou; já dá para enviar.
template.rejectedReprovou. O payload traz rejected_reason.
template.pausedPausado por qualidade baixa — ele para de enviar.
template.categorizedA Meta reclassificou. Muda o preço da conversa.

Enviar por nome​

curl -X POST https://api.bzapper.com.br/messages/template \
-H "Authorization: Bearer $BZAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "+5511999999999", "template": "pedido_confirmado",
"variables": ["Ana", "10432"], "header_variables": ["10432"] }'

O id que a Meta deu ao template nunca aparece. O language pode ser omitido quando o nome existe num só idioma; se existir em mais de um, respondemos 409 template_ambiguous em vez de escolher por você — mandar no idioma errado é pior que perguntar.

A quantidade de variables precisa bater com o template aprovado. Conferimos antes de gastar a chamada: divergência é a causa nº 1 do erro 132000 da Meta.

Código de verificação (OTP)​

Na categoria AUTHENTICATION a Meta escreve o texto sozinha e inclui o botão de copiar o código. Você só define a validade:

{ "name": "codigo_acesso", "language": "pt_BR",
"category": "AUTHENTICATION", "code_expiry_minutes": 10 }

E envia com code em vez de variables:

{ "to": "+5511999999999", "template": "codigo_acesso", "code": "738291" }

É assim que se manda um OTP fora da janela de 24 horas — que é o caso normal de um código de acesso, já que a pessoa quase nunca te escreveu antes.

Já tem templates na Meta?​

POST /templates/sync importa os que existem lá e não aqui — os que você criou no WhatsApp Manager antes de nos conhecer. Sem isso eles existiriam para a Meta e seriam invisíveis no painel, e o envio por nome diria que não existem.


Paridade de tipos​

Os 13 tipos suportáveis funcionam igual nos dois trilhos, com o mesmo corpo de request: texto, imagem, vídeo, áudio, documento, figurinha, localização, contato, reação, botões, lista, OTP e template.

Três diferenças que valem registro:

Botões e listas são melhores aqui. No trilho não oficial eles são instáveis e o bZapper sempre cai para um menu de texto numerado. Na Cloud API são nativos e renderizam. Acima dos limites da Meta (mais de 3 botões, título com mais de 20 caracteres, mais de 10 linhas de lista) caímos no mesmo menu de texto — a mensagem chega, só não chega clicável.

Enquete não existe. É a única lacuna de paridade, e ela é definitiva: a Cloud API não tem esse tipo. Um envio de enquete num projeto oficial falha com 422 official_poll_unsupported e aponta a alternativa, que é /messages/list.

O destino é sempre o telefone. O @lid é do WhatsApp multi-dispositivo: a Cloud API não endereça por ele nem tem o mapa LID→telefone. Um @lid em to falha com 422 official_lid_unsupported — no trilho não oficial, o mesmo to funciona (ver Contatos com @lid).


Quando a Meta recusa​

O bZapper traduz o código dela em instrução, preservando o número no fim para quando você precisar abrir um chamado com eles:

O que a Meta dizO que você lê
131047A janela de 24 horas fechou: use um template.
131026O WhatsApp não conseguiu entregar a este número.
131049A Meta optou por não entregar (contato saturado de marketing).
131042Elegibilidade da conta — quase sempre é o cartão faltando.
132000A quantidade de variáveis não bate com o template.
132015A Meta pausou o template por qualidade baixa.
130429O número bateu no limite diário de conversas iniciadas.

Duas dessas mudam o estado da conta sozinhas: elegibilidade recusada volta a marcar a conta como aguardando pagamento (e o painel mostra o passo de novo, com a instrução), e bloqueio por política a suspende.


Perfil comercial​

É o cartão de visita do número — o que o seu cliente vê ao abrir a conversa: a frase curta abaixo do nome, descrição, endereço, e-mail, setor, até dois sites e a foto. Tudo se edita daqui, sem abrir o WhatsApp Manager.

curl https://api.bzapper.com.br/official/profile -H "Authorization: Bearer $BZAPPER_KEY"

curl -X PUT https://api.bzapper.com.br/official/profile \
-H "Authorization: Bearer $BZAPPER_KEY" \
-H "Content-Type: application/json" \
-d '{ "about": "Atendimento das 8h às 18h", "description": "Loja de café especial.",
"email": "[email protected]", "vertical": "RETAIL",
"websites": ["https://loja.com.br"] }'
Mande o perfil inteiro

Na Meta, campo vazio não quer dizer "não mexi" — quer dizer "apague". Leia com GET, altere o que quiser e devolva tudo no PUT.

Conferimos os limites da Meta antes de enviar — frase de 139 caracteres, descrição de 512, endereço de 256, no máximo 2 sites e uma lista fechada de setores. Quando algo estoura, o 422 diz qual campo e por quanto, em vez do "Invalid parameter" da Meta.

  • Foto: POST /official/profile/photo (multipart, campo file, JPEG ou PNG até 5 MB; a Meta recomenda 640×640). O WhatsApp Business só tem a foto redonda — não existe foto de capa.
  • Nome de exibição: a troca do nome em si é feita no WhatsApp Manager e passa por análise da Meta. Depois de aprovada, falta a metade que é nossa: POST /official/profile/reregister. Até o número ser re-registrado, ele continua exibindo o nome antigo — é o passo que todo mundo esquece.

O perfil é do número, não da conta. Sem nada no caminho, as rotas acima agem sobre o número padrão; para outro número use /official/numbers/{id}/profile, /official/numbers/{id}/profile/photo e /official/numbers/{id}/profile/reregister.


Qualidade e limite​

A Meta atribui a cada número uma qualidade (GREEN, YELLOW, RED) e um limite diário de conversas iniciadas (TIER_250, TIER_1K, …). O limite sobe sozinho conforme o volume e a qualidade — e a qualidade cai quando as pessoas bloqueiam ou denunciam.

Os dois aparecem no painel e em GET /official/status, atualizados pelos webhooks da Meta. Você não precisa abrir o WhatsApp Manager para vê-los.

A regra vale nos dois trilhos

Template aprovado não é licença para disparo em massa. Qualidade em RED derruba o limite, e limite derrubado é entrega derrubada. O que protege o número continua sendo o mesmo do outro trilho: mandar para quem quer receber. Veja boas práticas de envio.