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 oficial | Trilho oficial | |
|---|---|---|
| Como o número entra | QR code no painel | Uma janela da Meta, uma vez |
| Iniciar conversa fria | Livre (com risco de bloqueio) | Só por template aprovado |
| Botões e listas | Caem em menu de texto | Nativos (dentro dos limites da Meta) |
| Enquete | Sim | Não existe na Cloud API |
| Grupos | Sim | Não existem na Cloud API |
| Campanhas | Texto com variações, ramp-up de 7 dias | Template aprovado, sem ramp-up |
| Agendamento | Sim | Sim — a janela de 24h é avaliada na hora do envio |
| Quem cobra a conversa | Ninguém | A Meta, no cartão do seu cliente |
| Risco de ban | Real | Praticamente 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:
| Evento | Quando |
|---|---|
template.approved | A Meta aprovou; já dá para enviar. |
template.rejected | Reprovou. O payload traz rejected_reason. |
template.paused | Pausado por qualidade baixa — ele para de enviar. |
template.categorized | A 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 diz | O que você lê |
|---|---|
131047 | A janela de 24 horas fechou: use um template. |
131026 | O WhatsApp não conseguiu entregar a este número. |
131049 | A Meta optou por não entregar (contato saturado de marketing). |
131042 | Elegibilidade da conta — quase sempre é o cartão faltando. |
132000 | A quantidade de variáveis não bate com o template. |
132015 | A Meta pausou o template por qualidade baixa. |
130429 | O 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"] }'
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, campofile, 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.
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.