Catalisa.
Building blocks/ComunicaçãoProdução

WPP Business

WhatsApp oficial da Meta, multi-tenant, do primeiro template ao atendimento humano

151
Endpoints
27
Entidades
1
Provedores
Tenant
Escopo
3025
Porta

Você fala com o seu cliente no canal que ele de fato lê, pelo caminho oficial da Meta, sem contratar uma plataforma de WhatsApp por fora e sem duplicar o cadastro dos seus clientes em mais um fornecedor.

Para quem é
  • Fintechs e financeiras que precisam avisar o cliente sobre boleto, proposta e cobrança no canal que ele lê
  • Varejo e e-commerce que atendem e vendem por WhatsApp com equipe de vários atendentes
  • Plataformas B2B que revendem atendimento por WhatsApp para dezenas de empresas clientes na mesma instalação
Substitui
  • Assinatura de uma plataforma de atendimento por WhatsApp cobrada por conversa ou por assento
  • Integração caseira direta com a Cloud API da Meta, com token, app e webhook geridos à mão por cliente
  • Camada própria de fila, retentativa, assinatura HMAC e log de webhook do WhatsApp
O que não é
  • Um bot de inteligência artificial — a resposta automática aqui casa palavra-chave, não interpreta linguagem
  • Um caminho não oficial via WhatsApp Web (Baileys, whatsapp-web.js) — isso é o building block wpp
  • Um gateway de pagamento — o BB monta a mensagem de cobrança, quem processa o dinheiro é o PSP
  • Um CRM — o contato aqui existe para conversar, não para gerir funil comercial

01Resumo executivo

O WPP Business conecta o seu produto ao WhatsApp pelo caminho oficial da Meta — a WhatsApp Business Platform, também chamada de Cloud API. Ele guarda as credenciais de cada empresa cliente, envia mensagem e template, recebe o que o cliente responde, e transforma tudo isso em evento que os seus sistemas conseguem consumir.

Na prática: uma financeira precisa avisar 4 mil clientes de que o boleto vence amanhã. Ela cria um template, espera a Meta aprovar, dispara uma campanha filtrada por etiqueta e acompanha quantas mensagens saíram, chegaram e foram lidas — sem escrever uma linha de integração com a Graph API e sem contratar uma plataforma de WhatsApp em separado.

Está em produção desde março de 2026. O primeiro webhook real foi configurado e validado em produção em 1º de abril de 2026 (tutorial de setup). É o maior building block do catálogo: 151 endpoints, 27 modelos de dados e 86 arquivos de teste.

AtributoValor
Identificadorwpp-business
CategoriaComunicação
EscopoTenant (exige organizationId no token em todas as rotas, exceto o webhook da Meta)
Porta (standalone)3025
Path alias@wpp-business
Prefixo HTTP/wpp-business
StatusProdução desde 2026-03
Depende dePostgreSQL (schema wpp_business), IAM, Webhooks Engine, Meta Graph API v25.0

02O problemanegócio

O cenário. No Brasil, o WhatsApp é onde a conversa com o cliente acontece. E-mail marketing tem taxa de abertura baixa, SMS é caro e desconfiado, e ligação quase ninguém atende. Toda empresa que fala com pessoa física acaba no WhatsApp — e, se a empresa é regulada, precisa que isso aconteça no canal oficial, com registro e não com o celular do vendedor.

O que trava hoje.

  • A Cloud API não é uma API simples. Para enviar a primeira mensagem você precisa de um Meta App, um App Secret, uma WABA verificada, um número registrado com PIN de dois fatores, um template aprovado e um webhook assinado com HMAC. Nada disso é opcional, e a documentação da Meta trata cada peça em uma página diferente.
  • Multi-tenant não existe no modelo da Meta. Cada empresa cliente tem a própria WABA e o próprio token. Se você atende trinta clientes, são trinta conjuntos de credenciais para guardar, criptografar e rotacionar — e um único webhook da Meta chegando para todos, que você precisa rotear.
  • A janela de 24 horas quebra a intuição do time. Fora dela, só template aprovado sai. O time de produto descobre isso em produção, quando a mensagem de resposta simplesmente não chega, e ninguém entende por quê.
  • Você reimplementa a mesma infraestrutura de eventos. Verificação de assinatura, log do payload cru, retentativa com backoff, filtro por tipo de mensagem, encaminhamento para o sistema certo. Isso é um serviço inteiro, e não é o seu produto.
  • A plataforma de WhatsApp vira mais um cadastro de cliente. Contratar um fornecedor separado significa manter a base de contatos em dois lugares e explicar, na auditoria, por que o dado do cliente também está lá.

O custo de não resolver. O WhatsApp está instalado em 98,3% dos smartphones brasileiros, e 97% dos donos de smartphone o acessam diariamente ou quase (Super Panorama Mobile Time/Opinion Box, junho de 2026, 4.138 respondentes, margem de erro 1,5 p.p. — Mobile Time). Do lado das empresas, 82% dos pequenos negócios brasileiros vendem pelo WhatsApp, contra 57% pelo Instagram e 30% pelo Facebook (Sebrae, Pulso dos Pequenos Negócios, 12ª edição, mais de 8.200 empreendedores, campo em fevereiro e março de 2026 — Agência Sebrae). O Brasil é o segundo maior mercado de WhatsApp do mundo, atrás só da Índia (Statista).

Ficar fora do canal, portanto, não é economia. E a alternativa não oficial — automatizar o WhatsApp Web com bibliotecas como Baileys ou whatsapp-web.js — viola os Termos de Serviço do WhatsApp Business, que proíbem literalmente "develop or use any applications that interact with our Business Services without our prior written consent" e reservam à Meta o direito de encerrar a conta, com a agravante de que, depois disso, "Company will not create another WhatsApp business account without our express written permission" (WhatsApp Business Terms of Service, consultado em 2026-08-16). Para uma operação de cobrança ou de atendimento, isso é perder o canal em um dia útil, sem recurso.


03Proposta de valornegócio

AntesDepois
Um conjunto de credenciais Meta por cliente, guardado à mãoWppBizApp e WppBizAccount por organização, com segredo cifrado em AES-256-GCM
Um webhook da Meta chegando misturado para todos os clientesO BB resolve a WABA do payload, valida a assinatura com o segredo daquele cliente e entrega o evento já escopado
"Por que a mensagem não chegou?" sem respostaGET /contacts/:id/conversation-window diz se a janela de 24h está aberta e quanto falta para fechar
Relay de eventos escrito na mão, sem retentativaDispatchers com filtro por tipo, condição JSONPath, assinatura HMAC, backoff e log de entrega
Base de contatos duplicada num fornecedor externoContatos, mensagens e conversas no seu banco, no mesmo organizationId do resto da plataforma

O onboarding cabe em duas chamadas. POST /apps registra o Meta App uma vez por organização. POST /devices/standalone recebe só o access token e o phoneNumberId e descobre sozinho a WABA, o negócio dono e o número — e ainda configura o webhook na Meta em seguida.

A janela de 24h deixa de ser folclore. O BB calcula a janela a partir da última mensagem recebida de verdade e devolve isOpen, expiresAt e remainingMinutes. O seu produto decide entre texto livre e template com dado, não com palpite.

O erro da Meta chega traduzido. META_TOKEN_EXPIRED, META_TOKEN_REVOKED, META_INSUFFICIENT_SCOPE e META_RATE_LIMIT vêm em details.code, junto do fbtrace_id. O painel decide o que mostrar ao usuário sem fazer parse de mensagem em inglês.

O evento sai pela mesma porta dos outros building blocks. Cada mensagem recebida vira um evento granular (wpp-biz.message.received.text, .image, .interactive...) que o Webhooks Engine entrega com retentativa. Quem já consome eventos do Commerce ou do Payments consome os do WhatsApp do mesmo jeito.

O atendimento humano tem lugar. Agentes, atribuição de conversa, transferência, notas internas e status de resolução são parte do BB — não um produto separado que você contrata por assento.


04Casos de uso reaisnegócio

Caso 1 — Uma financeira avisa o vencimento antes de o cliente atrasar Cenário ilustrativo

Contexto. Financeira de crédito pessoal com 40 mil contratos ativos e parcelas vencendo todo dia útil.

A dor. A régua de cobrança era e-mail e SMS. O e-mail não era aberto, o SMS era ignorado por parecer golpe, e a primeira conversa de verdade só acontecia depois do atraso — quando já havia juros, atrito e um cliente na defensiva. O time de cobrança gastava o dia ligando.

A solução com o BB. Um template UTILITY com o valor e a data da parcela é criado em POST /wpp-business/api/v1/templates e submetido à Meta em POST /templates/:id/submit. Os contatos entram por POST /contacts/bulk com etiqueta por faixa de vencimento. A régua diária dispara POST /campaigns/:id/execute. As respostas voltam pelo webhook da Meta, viram evento wpp-biz.message.received.text e um dispatcher as encaminha para a esteira de negociação.

O resultado. A conversa passa a começar antes do vencimento, no canal que o cliente lê. O time de cobrança para de discar e passa a responder — e cada mensagem enviada, entregue e lida fica registrada em wpp_biz_messages para a auditoria.

Caso 2 — Um varejista atende com dez pessoas no mesmo número Cenário ilustrativo

Contexto. Rede de lojas com um único número de WhatsApp comercial e dez atendentes.

A dor. O número vivia num celular na loja principal. Duas pessoas respondiam a mesma conversa, ninguém sabia o que já tinha sido dito, e quando o atendente saía de férias o histórico ia junto. Não havia como responder "quem atendeu esse cliente?".

A solução com o BB. Cada atendente vira um WppBizAgent ligado ao usuário do IAM (POST /agents). A conversa é atribuída em POST /inbox/assign, transferida em POST /inbox/:id/transfer com motivo registrado, e encerrada em POST /inbox/:id/resolve. A caixa de entrada compartilhada mostra prioridade, etiquetas e prazo de SLA.

O resultado. O histórico é da empresa, não do celular. A pergunta "quem falou com esse cliente e o que foi dito" vira uma consulta. Vale a ressalva honesta: hoje a atribuição é uma ação explícita da sua aplicação — mensagem chegando não cria conversa atribuída sozinha (ver §15).

Caso 3 — Uma plataforma B2B revende WhatsApp para as empresas dela Cenário ilustrativo

Contexto. ERP vertical que atende 60 empresas clientes e quer oferecer notificação por WhatsApp como módulo pago.

A dor. Cada cliente teria a própria WABA, o próprio número e o próprio token. A alternativa era subir uma instância por cliente, ou guardar 60 tokens de acesso total numa tabela sem criptografia e torcer.

A solução com o BB. Cada empresa cliente é uma Organization do IAM. POST /apps registra o Meta App e POST /accounts a WABA daquele cliente, com accessToken e appSecret cifrados em AES-256-GCM antes de tocar o banco. O webhook da Meta chega em um endpoint único, o BB resolve a conta pelo wabaId do payload e valida a assinatura com o segredo daquela conta específica.

O resultado. Uma instalação, 60 clientes, isolamento por organizationId assinado no token. Adicionar um cliente é uma chamada de API, não um provisionamento.

Caso 4 — Autenticação por WhatsApp em vez de SMS Referência de mercado

Contexto. A Meta criou uma categoria de template dedicada — AUTHENTICATION — justamente porque envio de código de verificação virou um dos usos mais comuns da plataforma, com formato de mensagem e botão de cópia padronizados (Authentication Templates).

A dor do mercado. SMS de OTP tem custo por mensagem, entrega irregular em algumas operadoras e é o vetor preferido de golpe de portabilidade. Empresas que dependem de OTP por SMS pagam caro por um canal que o próprio cliente aprendeu a desconfiar.

Como a Catalisa endereça. POST /wpp-business/api/v1/messages/send-otp monta os componentes do template de autenticação — corpo com o código e botão de URL com o sufixo — e envia pela Cloud API, registrando a mensagem como TEMPLATE no histórico. O código continua sendo gerado e validado pelo seu sistema; o BB entrega.

O resultado. O segundo fator sai pelo canal que o cliente já tem aberto, com o mesmo controle de permissão (WPP_BIZ_MESSAGES_SEND) e o mesmo registro das demais mensagens.

Caso 5 — Atendimento resolvido no WhatsApp muda o volume de conversão Referência de mercado

Contexto. Nos resultados do segundo trimestre de 2026, a Meta citou a Movida, locadora brasileira com quase 400 lojas, que colocou um agente no WhatsApp cobrindo todo o fluxo de reserva (transcrição da teleconferência de resultados da Meta, Q2 2026).

A dor do mercado. Reserva de locação é um formulário longo. No aplicativo ou no site, cada campo é uma chance de abandono; no telefone, é fila.

O resultado divulgado. Cliente recorrente conclui a reserva em até três mensagens. Em um mês, a Meta reporta 44% de aumento nas reservas diárias pelo WhatsApp na comparação anual, com 85% das conversas resolvidas inteiramente pela IA.

O que isso significa para quem avalia o WPP Business — com honestidade. A parte de IA desse caso é do agente da própria Meta, não deste building block: aqui a automação nativa casa palavra-chave, e resposta em linguagem natural vem de fora, pelo AI Engine (src/ai-engine) consumindo o evento e devolvendo por POST /messages/send (§15). O que o caso demonstra, e é o ponto, é a ordem de grandeza do canal no Brasil quando a jornada inteira cabe na conversa — e é essa jornada que o BB entrega: o envio, o recebimento, a janela, o registro e o evento.

Caso 6 — O evento do WhatsApp alimenta a esteira sem código de cola Cenário ilustrativo

Contexto. Operação que já usa Decision Platform para analisar proposta e quer que a resposta do cliente no WhatsApp destrave a etapa seguinte.

A dor. A integração natural seria escrever um serviço que escuta o webhook da Meta, filtra o que interessa, reenvia para a esteira, guarda o que falhou e tenta de novo. Isso é um projeto, não uma integração.

A solução com o BB. Um dispatcher em modo ADVANCED (POST /dispatchers) assina wpp-biz.message.received.interactive, aplica uma condição JSONPath sobre o payload e aponta para o endpoint da esteira, com signingSecret para HMAC e retryConfig com backoff exponencial. O Webhooks Engine entrega, registra e permite reprocessar em POST /dispatchers/:id/delivery-logs/:logId/retry.

O resultado. A cola some. O que sobra é uma configuração com log de entrega e botão de reprocessar.


05Mercado e diferenciaisnegócio

Panorama. Só existe um caminho oficial para o WhatsApp corporativo, e ele é da Meta. Desde 1º de julho de 2025 a Meta cobra por mensagem de template entregue, e não mais por conversa de 24 horas (WhatsApp Business Platform Pricing, consultado em 2026-08-16). A diferença entre os fornecedores está em quanto cobram por cima disso e em quanto de plataforma entregam junto.

De um lado ficam os provedores de acesso, com camada fina: a Cloud API direta e a 360dialog, que declara literalmente "no markups on Meta fees" e cobra €49 a €249 por mês por número (360dialog, consultado em 2026-08-16). No meio fica a Twilio, que publica tabela completa: US$ 0,005 por mensagem, na entrada e na saída, somados à tarifa da Meta (Twilio, consultado em 2026-08-16). Do outro lado ficam as plataformas completas — Take Blip, Zenvia, Infobip, Gupshup, Sinch — que somam construtor de fluxo, atendimento humano e campanha.

Duas observações que mudam a leitura do mercado, e que raramente aparecem em comparativo:

  • Metade dos grandes não publica preço de WhatsApp. Infobip, Sinch e Gupshup remetem a contato comercial; a página de preços da Gupshup responde 404 (verificado em 2026-08-16). Não é falha de busca, é o modelo comercial: você paga conforme o seu poder de barganha.
  • Os fornecedores brasileiros ainda cobram por conversa, não por mensagem. A Zenvia publica R$ 0,47 a R$ 0,55 por mensagem de empresa, com pacote mínimo de R$ 100 por mês e setup de R$ 649 (Zenvia); a Take Blip publica R$ 1,25 a R$ 1,40 por conversa adicional e R$ 100 a R$ 150 por agente por mês, sem publicar a mensalidade base (Blip) — ambos consultados em 2026-08-16. Como a Meta migrou para cobrança por mensagem, esses modelos não são diretamente comparáveis com a tarifa de origem.

O WPP Business não tenta ser a melhor plataforma de WhatsApp do mercado. Ele é a peça de WhatsApp de uma plataforma que o cliente já contratou por outro motivo — e que por isso não cobra o cadastro do cliente duas vezes.

CritérioCatalisa WPP BusinessCloud API direta (Meta)Twilio360dialogTake Blip
Custo acima da tarifa da MetaPrecificação em definição (§6)NenhumUS$ 0,005 por mensagem, entrada e saída€49–249/mês por número, sem margem por mensagemPor conversa (R$ 1,25–1,40 adicional) e por agente (R$ 100–150/mês)
Preço público e completoEm definiçãoSimSimSimParcial — base não publicada
Cobra também na mensagem recebidaNãoNãoSimNãoModelo de conversa
Multi-tenant nativo (várias WABAs isoladas)Sim, por organizationId no tokenVocê constróiPor subconta, você organizaPor chave de API, você organizaModelo de contrato, não de API
Templates, campanha e contatos inclusosSimNãoProduto separadoNãoSim
Caixa de entrada com agentesSim, no mesmo BB, sem cobrança por assentoNãoProduto separado (Flex)NãoSim, cobrado por assento
Construtor visual de fluxoNão (ver §15)NãoStudioNãoSim, é o forte deles
Relay de eventos com retentativa e logSim, via Webhooks EngineVocê constróiSimParcialSim
Bot com IANão (ver §15)NãoSim, integrávelNãoSim
SMS, voz e e-mail no mesmo contratoSó e-mail, em BB separadoNãoSim, é o forte delesNãoSim
Operação, nota fiscal e suporte no BrasilSimNãoParceirosNãoSim, é o forte deles
Dado do cliente fora da sua plataformaNão saiNão saiSaiSaiSai

Valores públicos consultados em 2026-08-16 nas páginas de preço dos próprios fornecedores. Preço muda com frequência e varia por país, volume e negociação — confira na data da sua análise. A tarifa da Meta está em WhatsApp Business Platform Pricing.

Nossos diferenciais

  1. A organização do IAM é a unidade de isolamento, não a instalação. Cada empresa cliente tem WppBizApp e WppBizAccount próprios, com token e App Secret cifrados, e o webhook único da Meta é roteado pelo wabaId do payload até a conta certa. Um concorrente só chega nisso se você construir a camada de tenancy por cima dele — que é exatamente o trabalho que ele te vendeu para não fazer.
  2. O WhatsApp entra na mesma malha de eventos do resto do catálogo. Um dispatcher do WPP Business vira uma inscrição no Webhooks Engine, com filtro JSONPath, HMAC, backoff e log de entrega reaproveitáveis. Quem já integrou o Commerce integra o WhatsApp com o mesmo código de recepção.
  3. O erro da Meta chega classificado. Token expirado, token revogado, escopo insuficiente e limite de taxa vêm em details.code com o fbtrace_id junto. Isso parece detalhe até a primeira madrugada em que um token de system user expira e o time precisa descobrir se é permissão, credencial ou throttle.
  4. Não há um segundo cadastro do seu cliente em um fornecedor de mensageria. Contato, mensagem e conversa ficam no mesmo banco, no mesmo tenant, sob a mesma política de retenção do resto da plataforma.

Quando escolher o concorrente. Se o seu problema é construir jornada conversacional complexa com pessoa não técnica desenhando o fluxo, a Take Blip e a Twilio Studio entregam isso hoje e o WPP Business não entrega — nosso construtor de fluxo é o WhatsApp Flows da própria Meta, editado por JSON, e não temos editor visual. Se você precisa de bot com IA respondendo em linguagem natural de caixa, aqui a automação casa palavra-chave e ponto; a Gupshup e a Blip têm produto pronto, e a própria Meta agora vende os Business Agents com cobrança por token. Se você precisa de SMS, voz e WhatsApp no mesmo contrato e no mesmo SLA, a Twilio, a Infobip e a Sinch são a escolha óbvia, e nós só temos e-mail em building block separado.

Se o seu volume é altíssimo e você já tem engenharia para operar a integração, a Cloud API direta ou a 360dialog vão custar menos — não existe intermediário mais barato que a ausência de intermediário, e o modelo de €49 por número da 360dialog tem margem marginal que tende a zero conforme o volume cresce. Se você precisa de entrada self-serve barata para uma operação pequena no Brasil, o Blip Go a R$ 299 por mês e o plano de entrada da Zenvia a R$ 100 por mês resolvem sem ciclo de vendas.

E se a sua operação depende de catálogo de produtos sincronizado com o Commerce Manager ou de pagamento processado dentro do WhatsApp, leia a §15 antes de decidir: essas duas frentes existem no modelo de dados mas não estão completas aqui.

Uma nota de diligência que vale para os dois lados: ao escolher fornecedor de canal, olhe a saúde dele. A Zenvia saiu do Nasdaq em março de 2026, em deslistagem voluntária que ela mesma justificou pela ausência de mercado ativo de negociação das ações (PR Newswire, consultado em 2026-08-16), e com isso deixou de publicar demonstrações financeiras. A Bird cortou cerca de 20% do quadro global em maio de 2026 e declarou foco no mercado americano. Isso não os desqualifica — mas se o WhatsApp é canal crítico da sua operação, a continuidade do fornecedor entra na conta junto com o preço.


06Modelo de cobrança e ROInegócio

Unidade de cobrança. A precificação Catalisa do WPP Business está em definição. O que já é certo é a estrutura: a tarifa da Meta é repassada, porque quem cobra por mensagem é a Meta e não a Catalisa.

Como a Meta cobra hoje. Desde 1º de julho de 2025, o modelo é por mensagem de template entregue — a documentação é literal: "You are only charged when a template message is delivered". Conversas de serviço iniciadas pelo cliente são gratuitas desde 1º de novembro de 2024, e mensagens que não são template são gratuitas desde que enviadas dentro de uma janela de atendimento aberta (WhatsApp Business Platform Pricing, consultado em 2026-08-16). O preço varia por categoria e por país do destinatário.

Estimativa, não tabela oficial. A Meta publica a tabela do Brasil em arquivo dentro do WhatsApp Manager, não em página aberta. Triangulando fornecedores públicos em 2026-08-16, o MARKETING no Brasil fica na ordem de US$ 0,0625 por mensagem e o UTILITY, na ordem de US$ 0,0068 — uma diferença de cerca de nove vezes. Trate como ordem de grandeza para orientar conversa e baixe a tabela oficial da sua conta antes de fechar qualquer número em proposta. O valor de AUTHENTICATION diverge entre as fontes e não está incluído aqui de propósito.

Duas mudanças da Meta com data marcada que precisam entrar no seu planejamento

DataO que mudaFonte
1º/10/2026A Meta passa a cobrar por resposta enviada dentro da janela de 24 horas, inclusive quando foi o cliente que iniciou. No Brasil, o valor noticiado é R$ 0,035 por mensagem, equivalente à tarifa de utilidade. Vale só para a WhatsApp Business API.Mobile Time, consultado em 2026-08-16. Corroborado por vários provedores; não localizamos a página da própria Meta com o anúncio — confirme com a Meta ou com o seu provedor antes de reprecificar.
30/06/2027Prazo final para migrar a WABA para faturamento em reais. A localização começou em 1º/07/2026 e, depois do prazo, contas não migradas param de ter mensagem entregue.Updates to Pricing, consultado em 2026-08-16

A primeira delas reprecifica o custo de operação de atendimento, que hoje é praticamente zero — reportagem sobre o mercado brasileiro cita um cliente cuja conta passaria de perto de zero para a ordem de US$ 100 mil por mês (Mobile Time, consultado em 2026-08-16). A segunda favorece quem já fatura em reais: contratos em euro ou dólar com provedor estrangeiro entram em conflito com esse relógio.

O que dispara custo.

DriverPor quê
Mensagens de template entreguesÉ a unidade de cobrança da Meta, com preço por categoria e por país
Respostas dentro da janela de atendimentoGratuitas hoje; passam a ser cobradas a partir de 1º/10/2026
Conversas registradasO BB grava cada conversa em wpp_biz_conversations com category, billable e pricingModel vindos da própria Meta — é a base de rateio por cliente
Números de telefone conectadosCada número tem limite de envio, avaliação de qualidade e custo de verificação próprios
Eventos entregues por dispatcherEntrega, retentativa e log passam pelo Webhooks Engine

O detalhe que mais destrói orçamento. A categoria do template não é escolha sua. Desde 9 de abril de 2025, quando você marca UTILITY e a Meta discorda, ela aprova o template como MARKETING em vez de rejeitar (Template Categorization, consultado em 2026-08-16). Com a diferença de cerca de nove vezes entre as duas categorias no Brasil, um template mal escrito multiplica o custo da régua inteira sem gerar um único erro na sua aplicação. Por isso GET /wpp-business/api/v1/analytics/conversations/stats agrupa por category e por originType: sem esse recorte, a fatura da Meta é um número só.

Comparação de custo — cenário nomeado: operação brasileira com 200 mil mensagens de template por mês (150 mil UTILITY, 50 mil MARKETING), 6 números conectados e 12 atendentes.

Catalisa WPP BusinessCloud API direta360dialogTwilioTake Blip
Tarifa da MetaRepassadaDiretaRepassada sem margemRepassadaEmbutida no modelo de conversa
Custo da camadaPrecificação em definiçãoZero em licença~€49 a €249 por número, por mês+ US$ 0,005 por mensagem, entrada e saídaBase não publicada + R$ 1,25–1,40 por conversa adicional
Custo por atendenteNenhumFlex, produto separadoR$ 100–150 por agente, por mês
Ordem de grandeza da camada, no cenárioEm definiçãoR$ 0 em licençaCentenas a poucos milhares de reaisOrdem de US$ 1.000 só nas 200 mil de saída, mais o que entrarMilhares de reais, com 12 assentos
Engenharia para multi-tenantInclusa4 a 8 semanas, estimativa internaFora do escopoFora do escopoInclusa
Engenharia para relay de eventosInclusa2 a 4 semanas, estimativa internaParcialInclusaInclusa

As ordens de grandeza acima são cálculo nosso sobre os preços públicos consultados em 2026-08-16, não proposta de nenhum fornecedor. As semanas de engenharia são estimativa interna da Catalisa, não medição. Infobip, Sinch e Gupshup ficaram fora da tabela porque não publicam preço de WhatsApp.

ROI. O retorno não está na linha de licença — a Cloud API direta será sempre a opção de menor tarifa, e a 360dialog fica muito perto disso. Está em duas coisas. A primeira é a engenharia que não é gasta: uma integração própria que atenda mais de um cliente precisa, no mínimo, de cofre de credencial por tenant, roteamento de webhook por WABA, verificação HMAC, registro de mensagem e status, controle da janela de 24 horas e camada de retentativa — cada item é código que alguém escreve, testa e mantém por anos. A segunda é o custo evitado que ninguém orça: com o MARKETING custando cerca de nove vezes o UTILITY no Brasil, e com a Meta recategorizando template sem rejeitá-lo, uma régua de 150 mil mensagens transacionais mal categorizada custa ordens de grandeza a mais em silêncio. Ver a categoria efetiva por conversa é o que transforma isso em problema detectável.


07Arquitetura

                  HTTP (Bearer JWT ou X-API-Key)         Meta → webhook (sem auth)
                              │                                     │
  ┌───────────────────────────┴─────────────────────────────────────┴─────────────┐
  │ Hono app  basePath('/wpp-business')                                            │
  │                                                                                │
  │  /api/v1/apps          credenciais do Meta App        /api/v1/webhook   ← Meta │
  │  /api/v1/accounts      WABA, números, webhook, Meta   /api/v1/webhook/logs     │
  │  /api/v1/devices       onboarding "modo B"            /api/v1/dispatchers      │
  │  /api/v1/messages      envio e histórico              /api/v1/analytics        │
  │  /api/v1/templates     cache local dos templates      /api/v1/agents           │
  │  /api/v1/contacts      contatos e etiquetas           /api/v1/inbox            │
  │  /api/v1/campaigns     disparo em massa               /api/v1/catalogs         │
  │  /api/v1/automations   resposta por palavra-chave     /api/v1/orders           │
  │  /api/v1/flows         WhatsApp Flows                 /api/v1/payments         │
  │  /api/v1/media         upload e download na Meta      /api/v1/short-links      │
  │  /health               sonda                                                   │
  └───────────────────────────┬────────────────────────────────────────────────────┘
                              │ Zod parse → ResultAsync<T, AppError>
  ┌───────────────────────────┴────────────────────────────────────────────────────┐
  │ services/  (19)  AccountService · MessageService · WebhookService · Template…   │
  └──────┬──────────────────────────────────────────────────┬──────────────────────┘
         │                                                  │
  ┌──────┴────────────────────────┐        ┌────────────────┴─────────────────────┐
  │ repositories/ (22) → Prisma   │        │ providers/cloud-api                  │
  │ PostgreSQL, schema            │        │   CloudApiClient                     │
  │ "wpp_business" (27 modelos)   │        │   graph.facebook.com/v25.0           │
  │ accessToken e appSecret       │        │   retry ×3, HMAC timing-safe,        │
  │ cifrados em AES-256-GCM       │        │   erro da Meta → details.code        │
  └───────────────────────────────┘        └──────────────────────────────────────┘

O caminho de uma mensagem recebida — o fluxo mais importante do BB:

  Meta  ──POST /wpp-business/api/v1/webhook──▶  webhook.router
                                                     │
                          1. lê entry[0].id  →  wabaId
                          2. AccountRepository.findByWabaIdGlobal(wabaId)
                          3. decifra o appSecret da conta (WppBizApp ou legado)
                          4. HMAC-SHA256 do corpo cru × x-hub-signature-256
                             │  falhou → 401. É o único 401 que a Meta recebe.
                             ▼
                          WebhookService.processWebhook
                             │
        ┌────────────────────┼──────────────────────────────────────┐
        ▼                    ▼                                      ▼
   grava WppBizWebhookLog   messages                        outros 9 campos
   (payload, headers,        │                              (template status,
    IP, tamanho, tempo)      ├─ get-or-create WppBizContact  qualidade, flows,
                             ├─ grava WppBizMessage          alertas, segurança)
                             ├─ publica wpp-biz.message.received     │
                             │        + evento granular por tipo     │
                             ├─ DispatcherService.evaluateDispatchers│
                             ├─ AutomationService.processMessage     │
                             └─ statuses → atualiza status da        │
                                mensagem + WppBizConversation        │
                                                                     ▼
                                                        publica evento e pronto
                                                        (não persiste — ver §15)

Decisões não óbvias.

  • O webhook responde 200 mesmo quando o processamento falha. A Meta reenvia payload em caso de erro, e reenvio em massa durante uma falha de banco transforma incidente em avalanche. Só assinatura inválida devolve 401. O erro real fica no WppBizWebhookLog, com o payload cru guardado para reprocessar em POST /webhook/logs/:id/replay.
  • O appSecret é resolvido por conta, não por variável de ambiente. Cada tenant tem o próprio Meta App. Um segredo global tornaria impossível hospedar duas empresas com apps diferentes — e é justamente esse o caso de uso.
  • A criptografia é aplicada na borda do repositório, não no serviço. AccountRepository cifra na escrita e decifra na leitura, então mais de trinta pontos de consumo continuam vendo o token em texto puro sem saber que ele está cifrado no banco. decryptSecretIfEncrypted deixa linhas legadas em texto puro passarem, o que permitiu ligar a criptografia antes de rodar a migração dos dados.
  • A verificação HMAC usa timingSafeEqual. Comparar assinatura com === vaza informação por tempo de resposta. É uma linha de código e elimina a classe inteira do problema.
  • 429 da Meta não é retentado. O cliente tenta de novo até três vezes com backoff exponencial em erro transitório, mas limite de taxa é explicitamente excluído: insistir estende a janela de throttle em vez de encurtá-la. O erro sobe como META_RATE_LIMIT para o chamador decidir.
  • Templates têm duas portas propositalmente. /api/v1/templates/* opera o cache local (WppBizTemplate), útil para listar rápido e para amarrar campanha. /api/v1/accounts/:id/templates/* fala direto com a Meta, que é a fonte de verdade sobre aprovação. Misturar as duas produziria um cache que mente sobre o status de aprovação.
  • A versão da Graph API é fixa no código (v25.0), não configurável por ambiente. Subir de versão é mudança de código com teste, não flag — a Meta muda contrato entre versões, e a v21.0 chegou a parar de entregar texto livre.

Monolito vs. standalone. Em monolito, o app é montado com os demais e resolvido pelo container TypeDI. Em standalone — o modo usado em produção — sobe na porta 3025 com prefixo /wpp-business. A diferença que importa: em standalone o WPP_BUSINESS_WEBHOOK_BASE_URL precisa apontar para a URL pública do serviço, porque é ela que o BB registra na Meta como callback.


08Conceitos e modelo de dados

Glossário

TermoSignifica
Meta App (WppBizApp)O aplicativo criado no Meta for Developers. Dono do App Secret que assina os webhooks. Um por organização, normalmente.
WABA (WppBizAccount)WhatsApp Business Account. A conta da empresa na Meta, dona dos números e dos templates.
Phone number (WppBizPhoneNumber)Um número conectado à WABA. Tem phoneNumberId na Meta e um UUID interno — as APIs de envio usam o UUID interno, não o ID da Meta.
TemplateMensagem pré-aprovada pela Meta. É o único jeito de iniciar conversa fora da janela de 24h. Tem categoria UTILITY, MARKETING ou AUTHENTICATION.
Janela de 24 horasPeríodo aberto pela última mensagem recebida do contato. Dentro dela sai texto livre, mídia e interativo. Fora dela, só template.
Conversa (WppBizConversation)O objeto de cobrança da Meta. Chega no status update com categoria, modelo de preço e se é faturável.
DispatcherRegra de encaminhamento de evento do WhatsApp para um endpoint externo. Vira uma inscrição no Webhooks Engine.
AutomaçãoResposta automática disparada por mensagem recebida, palavra-chave ou primeiro contato.
Agente (WppBizAgent)Atendente humano, ligado a um usuário do IAM, com capacidade máxima e status de disponibilidade.
Atribuição (WppBizConversationAssignment)O vínculo entre uma conversa e um agente, com prioridade, etiquetas e prazo de SLA.
FlowFormulário nativo do WhatsApp (WhatsApp Flows), definido por JSON e publicado na Meta.

Modelo de dados — schema wpp_business no PostgreSQL, 27 modelos.

Modelo PrismaTabelaPropósitoCampos-chave
WppBizAppwpp_biz_appsCredenciais do Meta AppappId, appSecret (cifrado), único (organizationId, appId)
WppBizAccountwpp_biz_accountsWABA da organizaçãowabaId, accessToken (cifrado), webhookVerifyToken, subscribedFields
WppBizPhoneNumberwpp_biz_phone_numbersNúmero conectadophoneNumberId (único global), displayNumber, qualityRating
WppBizMessagewpp_biz_messagesHistórico de mensagenswaMessageId, direction, status, type, sentAt/deliveredAt/readAt
WppBizContactwpp_biz_contactsContato do WhatsAppphone, waId, único (organizationId, phone)
WppBizContactTagwpp_biz_contact_tagsEtiqueta de contatoname, color, único (organizationId, name)
WppBizContactTagAssignmentwpp_biz_contact_tag_assignmentsContato ↔ etiquetaChave composta (contactId, tagId)
WppBizTemplatewpp_biz_templatesCache local do templatename, language, category, status, metaTemplateId, rejectionReason
WppBizCampaignwpp_biz_campaignsDisparo em massastatus, totalRecipients, sentCount, deliveredCount, readCount, failedCount
WppBizCampaignRecipientwpp_biz_campaign_recipientsDestinatário da campanhastatus, waMessageId, único (campaignId, contactId)
WppBizAutomationwpp_biz_automationsResposta automáticatrigger, conditions, action, actionConfig, enabled
WppBizWebhookLogwpp_biz_webhook_logsLog do webhook da Metapayload, requestHeaders, sourceIp, processingTimeMs, status
WppBizDispatcherwpp_biz_dispatchersEncaminhamento de eventomode, events, messageTypes, endpointUrl, signingSecret (cifrado), subscriptionId
WppBizFlowwpp_biz_flowsWhatsApp FlowmetaFlowId, status, categories, jsonDefinition, endpointUri
WppBizCatalogwpp_biz_catalogsCatálogo de produtosmetaCatalogId, isConnected — ver §15
WppBizProductwpp_biz_productsProduto do catálogoretailerId, price, availability, único (catalogId, retailerId)
WppBizOrderwpp_biz_ordersPedido feito no WhatsAppwaOrderId, status, totalAmount — ver §15
WppBizOrderItemwpp_biz_order_itemsItem do pedidoretailerId, quantity, unitPrice
WppBizPaymentConfigwpp_biz_payment_configsConfiguração de pagamentoprovider, enabledMethods, pixKey — ver §15
WppBizPaymentwpp_biz_paymentsCobrança enviadareferenceId, method, status, amount, paymentLink
WppBizConversationwpp_biz_conversationsConversa faturável da MetawaConversationId, category, pricingModel, billable, expiresAt
WppBizShortLinkwpp_biz_short_linksLink wa.me com mensagem prontashortUrl, prefilledMessage, clickCount, qrCodeData — ver §15
WppBizAgentwpp_biz_agentsAtendenteuserId (do IAM), status, maxConcurrent, currentLoad, skills
WppBizConversationAssignmentwpp_biz_conversation_assignmentsConversa atribuídaagentId, status, priority, tags, slaDeadlineAt
WppBizAgentNotewpp_biz_agent_notesNota interna do atendimentocontent, agentId
WppBizConversationTransferwpp_biz_conversation_transfersTransferência entre agentesfromAgentId, toAgentId, reason — ver §15
WppBizRoutingRulewpp_biz_routing_rulesRegra de distribuiçãopriority, conditions, action, actionConfig — ver §15

Enumerações principais

EnumValores
WppBizMessageDirectionINBOUND · OUTBOUND
WppBizMessageStatusPENDING · SENT · DELIVERED · READ · FAILED
WppBizMessageTypeTEXT · IMAGE · VIDEO · AUDIO · DOCUMENT · STICKER · LOCATION · CONTACTS · INTERACTIVE · TEMPLATE · REACTION
WppBizTemplateStatusDRAFT · PENDING · APPROVED · REJECTED
WppBizTemplateCategoryUTILITY · MARKETING · AUTHENTICATION
WppBizCampaignStatusDRAFT · SCHEDULED · EXECUTING · PAUSED · COMPLETED · FAILED
WppBizFlowStatusDRAFT · PUBLISHED · DEPRECATED · BLOCKED · THROTTLED
WppBizAgentStatusAVAILABLE · BUSY · OFFLINE · AWAY
WppBizConversationAssignmentStatusOPEN · ASSIGNED · WAITING · RESOLVED · CLOSED
WppBizPaymentStatusPENDING · PROCESSING · CAPTURED · FAILED · CANCELLED · REFUNDED · EXPIRED

A janela de 24 horas, que é a máquina de estado que mais gera bug de integração

    contato nunca escreveu              contato respondeu (inbound)
              │                                     │
              ▼                                     ▼
    ┌──────────────────┐   template aprovado  ┌──────────────────┐
    │  JANELA FECHADA  │ ───────────────────▶ │  JANELA ABERTA   │
    │                  │   e contato responde │                  │
    │ só POST          │                      │ POST /send       │
    │ /send-template   │ ◀─────────────────── │ /send-interactive│
    │ /send-otp        │   24h sem inbound    │ /send-template   │
    └──────────────────┘                      └──────────────────┘

    Consulte antes de decidir:
      GET /wpp-business/api/v1/contacts/:id/conversation-window?phoneNumberId=<uuid>
      → { isOpen, expiresAt, remainingMinutes, lastInboundAt }

    A janela é calculada a partir da ÚLTIMA mensagem INBOUND daquele contato
    naquele número. Enviar texto livre com a janela fechada devolve erro da Meta.

Ciclo de vida do template

   POST /templates                POST /templates/:id/submit
   (fromCache — grava local)      (envia para a Meta)
          │                                │
          ▼                                ▼
     ┌────────┐                      ┌──────────┐   Meta aprova   ┌──────────┐
     │ DRAFT  │ ───────────────────▶ │ PENDING  │ ──────────────▶ │ APPROVED │
     └────────┘                      └────┬─────┘                 └──────────┘
                                          │ Meta recusa
                                          ▼
                                     ┌──────────┐
                                     │ REJECTED │  rejectionReason preenchido
                                     └──────────┘   por POST /templates/:id/sync

   Atenção: o webhook `message_template_status_update` da Meta publica evento
   mas NÃO atualiza o status no banco. Use POST /templates/:id/sync ou leia
   direto da Meta em GET /accounts/:id/templates. Ver §15.

Ciclo de vida da campanha

   ┌───────┐  POST /:id/execute  ┌───────────┐  todos os lotes  ┌───────────┐
   │ DRAFT │ ──────────────────▶ │ EXECUTING │ ───────────────▶ │ COMPLETED │
   └───────┘                     └─────┬─────┘                  └───────────┘
                                       │ POST /:id/pause              ▲
                                       ▼                              │
                                  ┌────────┐   POST /:id/execute ─────┘
                                  │ PAUSED │
                                  └────────┘

   O envio roda em lotes de 50 com 1 segundo entre lotes, dentro do processo.
   `pause` só é verificado ENTRE lotes. `SCHEDULED` existe no enum mas não há
   agendador que o dispare. Ver §15.

09Referência da API

Prefixo: /wpp-business. Em staging, a base é https://wpp-business.bb.stg.catalisa.app/wpp-business.

Salvo indicação em contrário, toda rota exige authMiddleware (Bearer JWT do IAM ou X-API-Key), a permissão indicada e requireOrganization — token sem organizationId recebe 403. A única exceção é /api/v1/webhook, que é chamado pela Meta e se autentica por assinatura HMAC.

São 151 endpoints em 20 recursos, mais GET /wpp-business/health.

Meta Apps — /wpp-business/api/v1/apps (5)

MétodoRotaDescriçãoPermissão
POST/api/v1/appsRegistra um Meta App e cifra o App SecretWPP_BIZ_ACCOUNTS_MANAGE
GET/api/v1/appsLista os apps da organizaçãoWPP_BIZ_READ
GET/api/v1/apps/:idBusca um appWPP_BIZ_READ
PATCH/api/v1/apps/:idAtualiza nome ou App SecretWPP_BIZ_ACCOUNTS_MANAGE
DELETE/api/v1/apps/:idExclusão lógicaWPP_BIZ_ACCOUNTS_MANAGE

Contas WABA — /wpp-business/api/v1/accounts (27)

Conta

MétodoRotaDescriçãoPermissão
POST/api/v1/accountsCria a conta WABA (onboarding modo A)WPP_BIZ_ACCOUNTS_MANAGE
GET/api/v1/accountsLista contasWPP_BIZ_READ
GET/api/v1/accounts/:idBusca contaWPP_BIZ_READ
PATCH/api/v1/accounts/:idAtualiza token, nome ou app vinculadoWPP_BIZ_ACCOUNTS_MANAGE
DELETE/api/v1/accounts/:idExclusão lógicaWPP_BIZ_ACCOUNTS_MANAGE

Números da conta

MétodoRotaDescriçãoPermissão
POST/api/v1/accounts/:id/phone-numbersVincula um número à contaWPP_BIZ_ACCOUNTS_MANAGE
GET/api/v1/accounts/:id/phone-numbersLista os números da contaWPP_BIZ_READ

Webhook na Meta

MétodoRotaDescriçãoPermissão
GET/api/v1/accounts/:id/webhooksConsulta o estado da assinatura na MetaWPP_BIZ_READ
POST/api/v1/accounts/:id/webhooksAssina o app e a WABA nos campos escolhidosWPP_BIZ_ACCOUNTS_MANAGE
PUT/api/v1/accounts/:id/webhooksTroca os campos assinadosWPP_BIZ_ACCOUNTS_MANAGE
DELETE/api/v1/accounts/:id/webhooksRemove a assinatura no nível do appWPP_BIZ_ACCOUNTS_MANAGE
POST/api/v1/accounts/:id/regenerate-verify-tokenGera novo webhookVerifyTokenWPP_BIZ_ACCOUNTS_MANAGE

Informação da WABA

MétodoRotaDescriçãoPermissão
GET/api/v1/accounts/:id/waba-infoEstado da WABA na Meta (verificação, revisão)WPP_BIZ_READ

Templates direto na Meta — a Meta é a fonte de verdade nestas rotas

MétodoRotaDescriçãoPermissão
GET/api/v1/accounts/:id/templatesLista templates lendo a MetaWPP_BIZ_TEMPLATES_READ
POST/api/v1/accounts/:id/templatesCria o template direto na MetaWPP_BIZ_TEMPLATES_WRITE
DELETE/api/v1/accounts/:id/templatesApaga template na Meta por nomeWPP_BIZ_TEMPLATES_WRITE
POST/api/v1/accounts/:id/templates/:templateId/syncSincroniza um template local com a MetaWPP_BIZ_TEMPLATES_WRITE
PATCH/api/v1/accounts/:id/templates/:templateIdEdita componentes ou categoria na MetaWPP_BIZ_TEMPLATES_WRITE

Operações de número:phoneNumberId aqui é o ID da Meta, não o UUID interno

MétodoRotaDescriçãoPermissão
GET/api/v1/accounts/phone-numbers/:phoneNumberId/business-profileLê o perfil comercialWPP_BIZ_ACCOUNTS_MANAGE
PATCH/api/v1/accounts/phone-numbers/:phoneNumberId/business-profileAtualiza perfil comercialWPP_BIZ_ACCOUNTS_MANAGE
GET/api/v1/accounts/phone-numbers/:phoneNumberId/statusQualidade e estado do númeroWPP_BIZ_READ
POST/api/v1/accounts/phone-numbers/:phoneNumberId/registerRegistra o número na Cloud API com PINWPP_BIZ_ACCOUNTS_MANAGE
POST/api/v1/accounts/phone-numbers/:phoneNumberId/deregisterCancela o registroWPP_BIZ_ACCOUNTS_MANAGE
POST/api/v1/accounts/phone-numbers/:phoneNumberId/request-codePede código por SMS ou VOICEWPP_BIZ_ACCOUNTS_MANAGE
POST/api/v1/accounts/phone-numbers/:phoneNumberId/verify-codeConfirma o código recebidoWPP_BIZ_ACCOUNTS_MANAGE
POST/api/v1/accounts/phone-numbers/:phoneNumberId/two-step-verificationDefine o PIN de dois fatoresWPP_BIZ_ACCOUNTS_MANAGE
PATCH/api/v1/accounts/phone-numbers/:phoneNumberId/display-namePede troca do nome exibidoWPP_BIZ_ACCOUNTS_MANAGE

Onboarding simplificado — /wpp-business/api/v1/devices (1)

MétodoRotaDescriçãoPermissão
POST/api/v1/devices/standaloneOnboarding modo B: token + phoneNumberId + wabaId, o BB descobre o restoWPP_BIZ_ACCOUNTS_MANAGE

Mensagens — /wpp-business/api/v1/messages (7)

MétodoRotaDescriçãoPermissão
POST/api/v1/messages/sendTexto, mídia, localização ou interativo (exige janela aberta)WPP_BIZ_MESSAGES_SEND
POST/api/v1/messages/send-templateTemplate aprovado (abre a janela)WPP_BIZ_MESSAGES_SEND
POST/api/v1/messages/send-interactiveBotões, lista, CTA de URL ou flowWPP_BIZ_MESSAGES_SEND
POST/api/v1/messages/send-otpTemplate de autenticação com códigoWPP_BIZ_MESSAGES_SEND
POST/api/v1/messages/:id/mark-readMarca a mensagem como lida na MetaWPP_BIZ_MESSAGES_SEND
GET/api/v1/messagesLista mensagens (phoneNumberId, contactId, limit, offset)WPP_BIZ_MESSAGES_READ
GET/api/v1/messages/:idBusca uma mensagemWPP_BIZ_MESSAGES_READ

Contatos — /wpp-business/api/v1/contacts (12)

MétodoRotaDescriçãoPermissão
POST/api/v1/contactsCria contatoWPP_BIZ_CONTACTS_WRITE
GET/api/v1/contactsLista contatosWPP_BIZ_CONTACTS_READ
GET/api/v1/contacts/searchBusca por texto, telefone, e-mail, etiqueta e períodoWPP_BIZ_CONTACTS_READ
POST/api/v1/contacts/bulkImporta até 1.000 contatos por chamadaWPP_BIZ_CONTACTS_WRITE
GET/api/v1/contacts/:idBusca contatoWPP_BIZ_CONTACTS_READ
PATCH/api/v1/contacts/:idAtualiza contatoWPP_BIZ_CONTACTS_WRITE
DELETE/api/v1/contacts/:idExclusão lógicaWPP_BIZ_CONTACTS_WRITE
GET/api/v1/contacts/:id/conversation-windowEstado da janela de 24h (exige ?phoneNumberId=)WPP_BIZ_CONTACTS_READ
POST/api/v1/contacts/:id/tagsAtribui etiquetas ao contatoWPP_BIZ_CONTACTS_WRITE
GET/api/v1/contacts/tagsLista etiquetasWPP_BIZ_CONTACTS_READ
POST/api/v1/contacts/tagsCria etiquetaWPP_BIZ_CONTACTS_WRITE
DELETE/api/v1/contacts/tags/:tagIdRemove etiquetaWPP_BIZ_CONTACTS_WRITE

Templates (cache local) — /wpp-business/api/v1/templates (9)

MétodoRotaDescriçãoPermissão
POST/api/v1/templatesCria o template local como DRAFTWPP_BIZ_TEMPLATES_WRITE
GET/api/v1/templatesLista do cache localWPP_BIZ_TEMPLATES_READ
GET/api/v1/templates/:idBusca template localWPP_BIZ_TEMPLATES_READ
PATCH/api/v1/templates/:idEdita componentes ou categoria (bloqueado em PENDING)WPP_BIZ_TEMPLATES_WRITE
DELETE/api/v1/templates/:idExclusão lógicaWPP_BIZ_TEMPLATES_WRITE
POST/api/v1/templates/:id/submitEnvia o template à Meta e marca PENDINGWPP_BIZ_TEMPLATES_WRITE
POST/api/v1/templates/:id/syncPuxa o status atual da Meta para o cacheWPP_BIZ_TEMPLATES_WRITE
GET/api/v1/templates/analyticsMétricas de template na Meta (exige opt-in na WABA)WPP_BIZ_ANALYTICS_READ
GET/api/v1/templates/qualityNota de qualidade dos templates na MetaWPP_BIZ_ANALYTICS_READ

Campanhas — /wpp-business/api/v1/campaigns (8)

MétodoRotaDescriçãoPermissão
POST/api/v1/campaignsCria campanha (exige template APPROVED)WPP_BIZ_CAMPAIGNS_WRITE
GET/api/v1/campaignsLista campanhasWPP_BIZ_CAMPAIGNS_READ
GET/api/v1/campaigns/:idBusca campanha com contadoresWPP_BIZ_CAMPAIGNS_READ
PATCH/api/v1/campaigns/:idAtualiza (só em DRAFT ou SCHEDULED)WPP_BIZ_CAMPAIGNS_WRITE
DELETE/api/v1/campaigns/:idExclusão lógica (bloqueada em EXECUTING)WPP_BIZ_CAMPAIGNS_WRITE
POST/api/v1/campaigns/:id/executeResolve destinatários e começa a enviarWPP_BIZ_CAMPAIGNS_EXECUTE
POST/api/v1/campaigns/:id/pausePausa a campanha em execuçãoWPP_BIZ_CAMPAIGNS_EXECUTE
GET/api/v1/campaigns/:id/recipientsLista destinatários e status individualWPP_BIZ_CAMPAIGNS_READ

Automações — /wpp-business/api/v1/automations (6)

MétodoRotaDescriçãoPermissão
POST/api/v1/automationsCria automaçãoWPP_BIZ_AUTOMATIONS_WRITE
GET/api/v1/automationsLista automaçõesWPP_BIZ_AUTOMATIONS_READ
GET/api/v1/automations/:idBusca automaçãoWPP_BIZ_AUTOMATIONS_READ
PATCH/api/v1/automations/:idAtualiza automaçãoWPP_BIZ_AUTOMATIONS_WRITE
DELETE/api/v1/automations/:idExclusão lógicaWPP_BIZ_AUTOMATIONS_WRITE
POST/api/v1/automations/:id/toggleLiga ou desligaWPP_BIZ_AUTOMATIONS_WRITE

Dispatchers — /wpp-business/api/v1/dispatchers (8)

MétodoRotaDescriçãoPermissão
POST/api/v1/dispatchersCria dispatcher e a inscrição no Webhooks EngineWPP_BIZ_DISPATCHERS_WRITE
GET/api/v1/dispatchersLista dispatchersWPP_BIZ_DISPATCHERS_READ
GET/api/v1/dispatchers/:idBusca dispatcherWPP_BIZ_DISPATCHERS_READ
PATCH/api/v1/dispatchers/:idAtualiza filtros, destino e retentativaWPP_BIZ_DISPATCHERS_WRITE
DELETE/api/v1/dispatchers/:idRemove dispatcher e a inscriçãoWPP_BIZ_DISPATCHERS_WRITE
POST/api/v1/dispatchers/:id/togglePausa ou retoma a entregaWPP_BIZ_DISPATCHERS_WRITE
GET/api/v1/dispatchers/:id/delivery-logsLog de entrega (page, pageSize, status)WPP_BIZ_DISPATCHERS_READ
POST/api/v1/dispatchers/:id/delivery-logs/:logId/retryReenvia uma entrega que falhouWPP_BIZ_DISPATCHERS_WRITE

WhatsApp Flows — /wpp-business/api/v1/flows (9)

MétodoRotaDescriçãoPermissão
POST/api/v1/flowsCria o flow na MetaWPP_BIZ_FLOWS_MANAGE
GET/api/v1/flowsLista flowsWPP_BIZ_FLOWS_MANAGE
GET/api/v1/flows/:idBusca flowWPP_BIZ_FLOWS_MANAGE
PATCH/api/v1/flows/:idAtualiza nome, categorias ou endpointWPP_BIZ_FLOWS_MANAGE
PUT/api/v1/flows/:id/jsonSubstitui a definição JSON na MetaWPP_BIZ_FLOWS_MANAGE
POST/api/v1/flows/:id/publishPublica o flowWPP_BIZ_FLOWS_MANAGE
POST/api/v1/flows/:id/deprecateDescontinua o flowWPP_BIZ_FLOWS_MANAGE
DELETE/api/v1/flows/:idRemove o flowWPP_BIZ_FLOWS_MANAGE
POST/api/v1/flows/:id/sendEnvia mensagem interativa com o flowWPP_BIZ_MESSAGES_SEND

Catálogo e produtos — /wpp-business/api/v1/catalogs (11)

MétodoRotaDescriçãoPermissão
POST/api/v1/catalogsCria catálogo localWPP_BIZ_CATALOG_MANAGE
GET/api/v1/catalogsLista catálogosWPP_BIZ_CATALOG_READ
GET/api/v1/catalogs/:idBusca catálogoWPP_BIZ_CATALOG_READ
DELETE/api/v1/catalogs/:idExclusão lógicaWPP_BIZ_CATALOG_MANAGE
POST/api/v1/catalogs/:catalogId/productsCria produto localWPP_BIZ_CATALOG_MANAGE
GET/api/v1/catalogs/:catalogId/productsLista produtosWPP_BIZ_CATALOG_READ
GET/api/v1/catalogs/:catalogId/products/:pidBusca produtoWPP_BIZ_CATALOG_READ
PATCH/api/v1/catalogs/:catalogId/products/:pidAtualiza produtoWPP_BIZ_CATALOG_MANAGE
DELETE/api/v1/catalogs/:catalogId/products/:pidExclusão lógicaWPP_BIZ_CATALOG_MANAGE
POST/api/v1/catalogs/send-productEnvia mensagem de um produtoWPP_BIZ_CATALOG_MANAGE
POST/api/v1/catalogs/send-product-listEnvia lista de produtos por seçãoWPP_BIZ_CATALOG_MANAGE

Os dois send-* exigem que o catálogo tenha metaCatalogId, e nenhum endpoint preenche esse campo hoje. Leia a §15 antes de planejar com base neles.

Pedidos — /wpp-business/api/v1/orders (3)

MétodoRotaDescriçãoPermissão
GET/api/v1/ordersLista pedidosWPP_BIZ_ORDERS_READ
GET/api/v1/orders/:idBusca pedido com itensWPP_BIZ_ORDERS_READ
PATCH/api/v1/orders/:id/statusMuda o status do pedidoWPP_BIZ_ORDERS_MANAGE

Pagamentos — /wpp-business/api/v1/payments (9)

MétodoRotaDescriçãoPermissão
POST/api/v1/payments/configCria a configuração de pagamento da contaWPP_BIZ_PAYMENTS_MANAGE
GET/api/v1/payments/config/:accountIdLê a configuraçãoWPP_BIZ_PAYMENTS_READ
PATCH/api/v1/payments/config/:idAtualiza a configuraçãoWPP_BIZ_PAYMENTS_MANAGE
POST/api/v1/paymentsCria o registro de cobrança de um pedidoWPP_BIZ_PAYMENTS_MANAGE
GET/api/v1/paymentsLista cobrançasWPP_BIZ_PAYMENTS_READ
GET/api/v1/payments/:idBusca cobrançaWPP_BIZ_PAYMENTS_READ
PATCH/api/v1/payments/:id/statusMuda o status da cobrançaWPP_BIZ_PAYMENTS_MANAGE
POST/api/v1/payments/send-pixEnvia mensagem order_details com PIXWPP_BIZ_PAYMENTS_MANAGE
POST/api/v1/payments/send-boletoEnvia mensagem order_details com boletoWPP_BIZ_PAYMENTS_MANAGE

Analytics — /wpp-business/api/v1/analytics (2)

MétodoRotaDescriçãoPermissão
GET/api/v1/analytics/conversationsAnalytics de conversa da Meta (exige accountId, start, end)WPP_BIZ_ANALYTICS_READ
GET/api/v1/analytics/conversations/statsAgregação local por categoria e origemWPP_BIZ_ANALYTICS_READ

Mídia — /wpp-business/api/v1/media (4)

MétodoRotaDescriçãoPermissão
POST/api/v1/media/uploadSobe arquivo para a Meta (multipart/form-data)WPP_BIZ_MEDIA_MANAGE
GET/api/v1/media/:mediaId/urlObtém a URL temporária do arquivoWPP_BIZ_MEDIA_READ
GET/api/v1/media/:mediaId/downloadBaixa o binário pela MetaWPP_BIZ_MEDIA_READ
DELETE/api/v1/media/:mediaIdRemove o arquivo da MetaWPP_BIZ_MEDIA_MANAGE
MétodoRotaDescriçãoPermissão
POST/api/v1/short-linksCria link wa.me com mensagem prontaWPP_BIZ_SHORT_LINKS_MANAGE
GET/api/v1/short-linksLista linksWPP_BIZ_SHORT_LINKS_MANAGE
GET/api/v1/short-links/:idBusca linkWPP_BIZ_SHORT_LINKS_MANAGE
DELETE/api/v1/short-links/:idExclusão lógicaWPP_BIZ_SHORT_LINKS_MANAGE
POST/api/v1/short-links/:id/clickIncrementa o contador de cliquesWPP_BIZ_SHORT_LINKS_MANAGE

Agentes e regras de distribuição — /wpp-business/api/v1/agents (11)

MétodoRotaDescriçãoPermissão
POST/api/v1/agentsCria agente a partir de um usuário do IAMWPP_BIZ_AGENTS_MANAGE
GET/api/v1/agentsLista agentesWPP_BIZ_AGENTS_MANAGE
GET/api/v1/agents/availableLista agentes disponíveis, por menor cargaWPP_BIZ_AGENTS_MANAGE
GET/api/v1/agents/:idBusca agenteWPP_BIZ_AGENTS_MANAGE
PATCH/api/v1/agents/:idAtualiza nome, e-mail, capacidade e habilidadesWPP_BIZ_AGENTS_MANAGE
PATCH/api/v1/agents/:id/statusMuda disponibilidadeWPP_BIZ_AGENTS_MANAGE
DELETE/api/v1/agents/:idExclusão lógicaWPP_BIZ_AGENTS_MANAGE
POST/api/v1/agents/routing-rulesCria regra de distribuiçãoWPP_BIZ_AGENTS_MANAGE
GET/api/v1/agents/routing-rulesLista regrasWPP_BIZ_AGENTS_MANAGE
PATCH/api/v1/agents/routing-rules/:idAtualiza regraWPP_BIZ_AGENTS_MANAGE
DELETE/api/v1/agents/routing-rules/:idExclusão lógicaWPP_BIZ_AGENTS_MANAGE

Caixa de entrada — /wpp-business/api/v1/inbox (8)

MétodoRotaDescriçãoPermissão
POST/api/v1/inbox/assignCria ou atualiza a atribuição de uma conversaWPP_BIZ_INBOX_MANAGE
GET/api/v1/inboxLista atribuiçõesWPP_BIZ_INBOX_MANAGE
GET/api/v1/inbox/:idBusca atribuiçãoWPP_BIZ_INBOX_MANAGE
POST/api/v1/inbox/:id/transferTransfere para outro agente, com motivoWPP_BIZ_INBOX_MANAGE
POST/api/v1/inbox/:id/resolveMarca como resolvidaWPP_BIZ_INBOX_MANAGE
POST/api/v1/inbox/:id/closeFecha a conversaWPP_BIZ_INBOX_MANAGE
POST/api/v1/inbox/:id/notesAdiciona nota interna (só agente registrado)WPP_BIZ_INBOX_MANAGE
GET/api/v1/inbox/:id/notesLista notasWPP_BIZ_INBOX_MANAGE

Webhook da Meta e log — /wpp-business/api/v1/webhook (2 + 4)

MétodoRotaDescriçãoPermissão
GET/api/v1/webhookVerificação hub.challenge da MetaPública — casa o webhookVerifyToken da conta
POST/api/v1/webhookRecebe eventos da MetaPública — autentica por HMAC x-hub-signature-256
GET/api/v1/webhook/logsLista logs de webhookWPP_BIZ_WEBHOOKS_READ
GET/api/v1/webhook/logs/statsEstatísticas de processamentoWPP_BIZ_WEBHOOKS_READ
GET/api/v1/webhook/logs/:idBusca um log com o payload cruWPP_BIZ_WEBHOOKS_READ
POST/api/v1/webhook/logs/:id/replayReprocessa o payload guardadoWPP_BIZ_WEBHOOKS_WRITE

Saúde

MétodoRotaDescrição
GET/wpp-business/healthIdentificação e versão do serviço. Fora da contagem de 151.

Abaixo, os endpoints que um integrador usa primeiro, em detalhe.

POST /wpp-business/api/v1/apps

Registra o Meta App da organização. É o primeiro passo — sem ele, criar conta falha com APP_NOT_REGISTERED.

Request

{
  "name": "App WhatsApp da Financeira Exemplo",
  "appId": "1234567890123456",
  "appSecret": "o-app-secret-do-meta-for-developers"
}
CampoTipoObrigatórioDescrição
namestring (1–200)SimNome de exibição interno
appIdstringSimApp ID do Meta for Developers. Único por organização.
appSecretstringSimApp Secret. Cifrado em AES-256-GCM antes de tocar o banco e nunca devolvido.

Resposta 201 — o App Secret vem como booleano, não como valor.

{
  "data": {
    "id": "a1000000-0000-4000-8000-000000000001",
    "name": "App WhatsApp da Financeira Exemplo",
    "appId": "1234567890123456",
    "hasAppSecret": true,
    "createdAt": "2026-08-16T12:00:00.000Z"
  }
}

Erros

StatusQuando
400Corpo reprovado no Zod
403Token sem organizationId ou sem WPP_BIZ_ACCOUNTS_MANAGE
409appId já registrado nesta organização

POST /wpp-business/api/v1/devices/standalone

Onboarding modo B: você manda o access token, o phoneNumberId e o wabaId, e o BB descobre a WABA, o negócio dono e o número na Graph API, cria ou reaproveita a conta e ainda configura o webhook na Meta em seguida.

Request

{
  "accessToken": "EAAxxxxx...",
  "phoneNumberId": "9876543210987654",
  "wabaId": "1122334455667788",
  "name": "Número principal de atendimento"
}
CampoTipoObrigatórioDescrição
accessTokenstringSimToken de system user da Meta com os escopos de messaging e management
phoneNumberIdstringSimID do número na Meta
wabaIdstringNão, mas mande sempreA partir da Graph API v25.0 a Meta parou de expor a WABA na consulta direta ao número; sem ele o BB falha com META_WABA_ID_REQUIRED
namestring (≤200)NãoNome da conta. Sem ele, usa o nome verificado do número.
descriptionstring (≤2000)NãoDescrição livre

Resposta 201

{
  "data": {
    "bbDeviceId": "a3000000-0000-4000-8000-000000000003",
    "accountId": "a2000000-0000-4000-8000-000000000002",
    "wppBizAppId": "a1000000-0000-4000-8000-000000000001",
    "displayNumber": "+55 11 90000-0000"
  }
}

O bbDeviceId é o UUID interno do número — é ele que vai em phoneNumberId nas rotas de envio, não o ID da Meta.

Erros

Statusdetails.codeQuando
400APP_NOT_REGISTEREDO token foi emitido por um Meta App que a organização não registrou em POST /apps. O appId vem no corpo da resposta.
400META_WABA_ID_REQUIREDNão foi possível descobrir a WABA — mande wabaId no corpo
401META_TOKEN_INVALID / META_TOKEN_EXPIREDToken da Meta inválido, expirado ou revogado
403META_INSUFFICIENT_SCOPEFalta escopo no token da Meta

POST /wpp-business/api/v1/accounts/:id/webhooks

Configura o webhook na Meta. Faz duas chamadas: assina o Meta App na callback URL e inscreve a WABA no app.

Pré-requisitos. A conta precisa ter um WppBizApp vinculado (ou os campos legados appId/appSecret) e um webhookVerifyToken — gere com POST /accounts/:id/regenerate-verify-token. O serviço também precisa de WPP_BUSINESS_WEBHOOK_BASE_URL configurada.

Request

{ "fields": ["messages", "message_template_status_update", "phone_number_quality_update"] }
CampoTipoObrigatórioDescrição
fieldsstring[]Não, padrão ["messages"]Campos assinados. Valores aceitos: messages, message_template_status_update, message_template_quality_update, account_alerts, account_update, account_review_update, phone_number_name_update, phone_number_quality_update, security, flows, business_capability_update, template_category_update, order_status_update.

overrideCallbackUri não é mais aceito — o schema rejeita explicitamente. A URL de callback é derivada de WPP_BUSINESS_WEBHOOK_BASE_URL e é sempre <base>/wpp-business/api/v1/webhook.

Resposta 201

{
  "data": {
    "id": "a2000000-0000-4000-8000-000000000002",
    "subscribedFields": ["messages", "message_template_status_update", "phone_number_quality_update"],
    "webhookConfiguredAt": "2026-08-16T12:05:00.000Z",
    "webhookCallbackUrl": "https://wpp-business.bb.stg.catalisa.app/wpp-business/api/v1/webhook",
    "hasAccessToken": true,
    "hasAppSecret": true
  }
}

Erros

StatusQuando
400Conta sem app vinculado, sem webhookVerifyToken, ou WPP_BUSINESS_WEBHOOK_BASE_URL ausente
401A Meta recusou o token da conta ao inscrever a WABA
404Conta inexistente ou de outra organização

POST /wpp-business/api/v1/messages/send-template

Envia um template aprovado. É o único jeito de iniciar conversa com a janela de 24h fechada.

Request

{
  "phoneNumberId": "a3000000-0000-4000-8000-000000000003",
  "to": "5511999998888",
  "templateName": "aviso_vencimento",
  "languageCode": "pt_BR",
  "components": [
    {
      "type": "body",
      "parameters": [
        { "type": "text", "text": "Maria" },
        { "type": "text", "text": "R$ 412,90" },
        { "type": "text", "text": "20/08" }
      ]
    }
  ]
}
CampoTipoObrigatórioDescrição
phoneNumberIdstring (UUID)SimUUID interno do número (WppBizPhoneNumber.id), não o ID da Meta
tostringSimTelefone do destinatário no formato internacional, só dígitos (5511999998888)
templateNamestringSimNome exato do template aprovado na Meta
languageCodestringSimCódigo do idioma do template (pt_BR, en_US)
componentsobject[]NãoComponentes no formato da Cloud API. Obrigatório se o template tem variável.

Resposta 201 — o registro em wpp_biz_messages, já com o ID da Meta.

{
  "data": {
    "id": "0c8f3f7a-4d2b-4b71-9d1e-3a6c5e2f8b40",
    "waMessageId": "wamid.HBgNNTUxMTk5OTk5ODg4OBUCABEYEjc...",
    "direction": "OUTBOUND",
    "status": "SENT",
    "type": "TEMPLATE",
    "to": "5511999998888",
    "sentAt": "2026-08-16T12:10:00.000Z"
  }
}

O status evolui para DELIVERED e READ conforme a Meta manda os status updates pelo webhook. Se o envio falhar, a linha é gravada com status: "FAILED" e errorMessage, e a resposta HTTP carrega o erro.

Erros

Statusdetails.codeQuando
400Corpo reprovado no Zod, ou template rejeitado pela Meta (parâmetro faltando, nome errado)
401META_TOKEN_EXPIRED / META_TOKEN_REVOKEDToken da conta inválido — atualize em PATCH /accounts/:id
403META_INSUFFICIENT_SCOPEO token da Meta não tem whatsapp_business_messaging
404phoneNumberId não existe nesta organização
429META_RATE_LIMITLimite de envio da Meta atingido. Não é retentado automaticamente — recue e tente depois.

POST /wpp-business/api/v1/messages/send

Texto livre, mídia, localização e interativo. Só funciona com a janela de 24h aberta.

Request

{
  "phoneNumberId": "a3000000-0000-4000-8000-000000000003",
  "to": "5511999998888",
  "type": "TEXT",
  "content": { "body": "Recebi seu comprovante, já estou verificando." }
}
CampoTipoObrigatórioDescrição
phoneNumberIdstring (UUID)SimUUID interno do número
tostringSimDestinatário
typeTEXT | IMAGE | VIDEO | AUDIO | DOCUMENT | LOCATION | INTERACTIVESimTipo da mensagem
contentobjectSimO objeto que a Cloud API espera para aquele tipo. Vai direto no payload, sob a chave do tipo em minúsculas.

Exemplos de content por tipo:

// TEXT
{ "body": "texto da mensagem" }
// IMAGE — por link público ou por id vindo de POST /media/upload
{ "link": "https://exemplo.com/foto.jpg", "caption": "legenda" }
{ "id": "1234567890", "caption": "legenda" }
// DOCUMENT
{ "id": "1234567890", "filename": "boleto.pdf" }
// LOCATION
{ "latitude": -23.5613, "longitude": -46.6565, "name": "Loja Paulista" }

Erros — os mesmos de send-template, mais o mais comum de todos: a Meta recusa com 400 quando a janela está fechada. Consulte GET /contacts/:id/conversation-window antes.


GET /wpp-business/api/v1/contacts/:id/conversation-window

Diz se dá para mandar texto livre.

Query string

ParâmetroObrigatórioDescrição
phoneNumberIdSimUUID interno do número. Sem ele, 400.

Resposta 200

{
  "data": {
    "isOpen": true,
    "expiresAt": "2026-08-17T09:32:11.000Z",
    "remainingMinutes": 1289,
    "lastInboundAt": "2026-08-16T09:32:11.000Z"
  }
}

Com isOpen: false, remainingMinutes é 0 e expiresAt pode ser null quando o contato nunca escreveu. Nesse caso, a única saída é POST /messages/send-template.


POST /wpp-business/api/v1/webhook — chamado pela Meta

Você não chama este endpoint; a Meta chama. Documentado porque é o ponto que mais gera dúvida na configuração.

Autenticação. Sem JWT. O BB extrai entry[0].id (o wabaId), encontra a conta correspondente, decifra o App Secret dela e valida o x-hub-signature-256 com HMAC-SHA256 sobre o corpo cru, em comparação de tempo constante.

Respostas

StatusCorpoQuando
200{"status":"ok"}Processado
200{"status":"error","message":"..."}Falhou no processamento — de propósito, para a Meta não reenviar em massa. O erro fica no WppBizWebhookLog.
400{"error":"Invalid JSON"}Corpo não é JSON
401{"error":"Missing WABA ID in payload"}Sem entry[0].id
401{"error":"Account not found for WABA ID"}Nenhuma conta ativa com esse wabaId
401{"error":"No app secret configured for this account..."}Conta sem WppBizApp vinculado nem appSecret legado
401{"error":"Invalid webhook signature"}Assinatura não confere

Verificação inicialGET /wpp-business/api/v1/webhook?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=<n> devolve o challenge em texto puro quando o verify_token casa com o webhookVerifyToken de alguma conta ativa, e 403 caso contrário.


10Início rápido

Do zero à primeira mensagem entregue no WhatsApp de alguém.

Os comandos abaixo não foram executados na redação deste documento. Eles reproduzem o fluxo validado em produção em 1º de abril de 2026, registrado no tutorial de webhook. Credenciais de staging vêm de AMBIENTES.md — nunca use credencial de produção em script de exemplo.

Pré-requisitos do lado da Meta, que nenhuma API resolve por você: uma WABA verificada, um número registrado com PIN de dois fatores, um Meta App com o App Secret em mãos, e um template aprovado (o hello_world que a Meta cria por padrão serve para o teste).

1. Autenticar no IAM

BASE="https://wpp-business.bb.stg.catalisa.app/wpp-business/api/v1"

TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@catalisa.app",
    "password": "root123456",
    "organizationId": "b0000000-0000-0000-0000-000000000001"
  }' | jq -r .accessToken)

echo "${TOKEN:0:32}..."

2. Registrar o Meta App

APP_ID=$(curl -s -X POST "$BASE/apps" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "App de teste",
    "appId": "SEU_META_APP_ID",
    "appSecret": "SEU_META_APP_SECRET"
  }' | jq -r '.data.id')
{ "data": { "id": "a1000000-...", "appId": "SEU_META_APP_ID", "hasAppSecret": true } }

3. Conectar o número (modo B)

curl -s -X POST "$BASE/devices/standalone" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "accessToken": "SEU_TOKEN_DE_SYSTEM_USER",
    "phoneNumberId": "ID_DO_NUMERO_NA_META",
    "wabaId": "ID_DA_SUA_WABA"
  }' | jq
{
  "data": {
    "bbDeviceId": "a3000000-...",
    "accountId": "a2000000-...",
    "wppBizAppId": "a1000000-...",
    "displayNumber": "+55 11 90000-0000"
  }
}

Guarde os dois: PHONE=a3000000-... e ACCOUNT=a2000000-....

4. Configurar o webhook na Meta

O modo B já tenta configurar o webhook sozinho. Confirme, e configure à mão se não tiver ido:

curl -s "$BASE/accounts/$ACCOUNT/webhooks" -H "Authorization: Bearer $TOKEN" | jq

# se necessário:
curl -s -X POST "$BASE/accounts/$ACCOUNT/regenerate-verify-token" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.webhookVerifyToken'

curl -s -X POST "$BASE/accounts/$ACCOUNT/webhooks" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"fields":["messages"]}' | jq '.data | {subscribedFields, webhookCallbackUrl}'

5. Enviar o primeiro template

curl -s -X POST "$BASE/messages/send-template" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"phoneNumberId\": \"$PHONE\",
    \"to\": \"5511999999999\",
    \"templateName\": \"hello_world\",
    \"languageCode\": \"en_US\"
  }" | jq '.data | {id, waMessageId, status}'
{ "id": "0c8f3f7a-...", "waMessageId": "wamid.HBgNNTUxMTk5...", "status": "SENT" }

A mensagem chega no celular. É aqui que você vê acontecer.

6. Responder no celular e conferir que a resposta chegou

curl -s "$BASE/messages?phoneNumberId=$PHONE&limit=5" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | {direction, type, status, from, createdAt}'

A resposta aparece com direction: "INBOUND". Se não aparecer, o webhook não está entregando — confira o log:

curl -s "$BASE/webhook/logs?limit=5" -H "Authorization: Bearer $TOKEN" \
  | jq '.data[] | {status, errorMessage, sourceIp, createdAt}'

7. Confirmar que a janela abriu

CONTACT=$(curl -s "$BASE/contacts?limit=1" -H "Authorization: Bearer $TOKEN" | jq -r '.data[0].id')

curl -s "$BASE/contacts/$CONTACT/conversation-window?phoneNumberId=$PHONE" \
  -H "Authorization: Bearer $TOKEN" | jq
{ "data": { "isOpen": true, "remainingMinutes": 1439, "expiresAt": "..." } }

Com a janela aberta, POST /messages/send com texto livre passa a funcionar.


11Receitas

Publicar um template e usá-lo em campanha

Objetivo: sair do zero até uma campanha disparada para os contatos de uma etiqueta.

# 1. Criar o template no cache local, como DRAFT
TPL=$(curl -s -X POST "$BASE/templates" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"accountId\": \"$ACCOUNT\",
    \"name\": \"aviso_vencimento\",
    \"language\": \"pt_BR\",
    \"category\": \"UTILITY\",
    \"components\": [
      {\"type\":\"BODY\",\"text\":\"Olá {{1}}, sua parcela de {{2}} vence em {{3}}.\"}
    ]
  }" | jq -r '.data.id')

# 2. Submeter à Meta — status vai para PENDING
curl -s -X POST "$BASE/templates/$TPL/submit" -H "Authorization: Bearer $TOKEN" | jq '.data.status'

# 3. Aguardar a aprovação e sincronizar o status local
curl -s -X POST "$BASE/templates/$TPL/sync" -H "Authorization: Bearer $TOKEN" \
  | jq '.data | {status, rejectionReason}'

# 4. Etiquetar os contatos-alvo
TAG=$(curl -s -X POST "$BASE/contacts/tags" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"vence-20-08","color":"#F97316"}' | jq -r '.data.id')

# 5. Criar e disparar a campanha
CAMP=$(curl -s -X POST "$BASE/campaigns" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"name\": \"Aviso de vencimento 20/08\",
    \"templateId\": \"$TPL\",
    \"phoneNumberId\": \"$PHONE\",
    \"filters\": {\"tagIds\": [\"$TAG\"]}
  }" | jq -r '.data.id')

curl -s -X POST "$BASE/campaigns/$CAMP/execute" -H "Authorization: Bearer $TOKEN" | jq '.data.status'

# 6. Acompanhar
curl -s "$BASE/campaigns/$CAMP" -H "Authorization: Bearer $TOKEN" \
  | jq '.data | {status, totalRecipients, sentCount, failedCount}'

Armadilhas.

  • filters vazio significa a base inteira. O único filtro implementado é tagIds. Qualquer outra chave é ignorada e a campanha vai para todos os contatos da organização. Confira o totalRecipients logo depois do execute, antes de tomar café.
  • A campanha só sai com o template APPROVED. O BB recusa com 400 se o status local não estiver aprovado — e o status local só muda com POST /templates/:id/sync, porque o webhook de aprovação da Meta não escreve no banco (§15).
  • A Meta recategoriza template, e isso custa caro. Desde abril de 2025, quando você pede UTILITY e a Meta discorda, ela aprova como MARKETING em vez de rejeitar — a sua aplicação não vê erro nenhum, e a fatura pode multiplicar por cerca de nove (§6). As causas mais comuns são conteúdo misturado (uma atualização de pedido com uma promoção junto) e conteúdo vago (um corpo que é só {{1}}, ou só "Parabéns!"). Sempre confira a categoria efetiva em GET /accounts/:id/templates depois da aprovação. Há 60 dias para recorrer da categorização (Template Categorization).
  • Reincidência tem escada de punição. A Meta aplica, em ordem: aviso — e a partir daí recategorização passa a ser instantânea, sem as 24 horas de aviso prévio; limitação de volume de UTILITY por pelo menos 7 dias; e, no limite, recategorização de todos os templates de utilidade da WABA para marketing, com criação de novos utility desabilitada por 7 dias, ou 30 em reincidência.
  • pause demora até um lote. O envio roda em lotes de 50 e o pause só é verificado entre lotes.
  • Não reinicie o serviço com campanha em andamento. O envio roda dentro do processo; um deploy no meio deixa os destinatários restantes em PENDING para sempre (§15).
  • deliveredCount e readCount não sobem. Os envios de campanha não criam registro em wpp_biz_messages, então o status update da Meta não encontra o destinatário para atualizar. Use sentCount e failedCount, que são confiáveis (§15).

Reprocessar um webhook que falhou

Objetivo: descobrir por que um evento da Meta não virou mensagem e tentar de novo.

# 1. Achar o log com falha
curl -s "$BASE/webhook/logs?status=FAILED&limit=10" -H "Authorization: Bearer $TOKEN" \
  | jq '.data[] | {id, errorMessage, phoneNumberId, createdAt}'

# 2. Ver o payload cru
curl -s "$BASE/webhook/logs/$LOG_ID" -H "Authorization: Bearer $TOKEN" | jq '.data.payload'

# 3. Reprocessar
curl -s -X POST "$BASE/webhook/logs/$LOG_ID/replay" -H "Authorization: Bearer $TOKEN" | jq

# 4. Estatísticas gerais, para saber se é caso isolado ou sistêmico
curl -s "$BASE/webhook/logs/stats" -H "Authorization: Bearer $TOKEN" | jq

Armadilhas.

  • IGNORED quase sempre é número desconhecido. O log fica IGNORED quando o phone_number_id do payload não bate com nenhum WppBizPhoneNumber. Confira se o número foi vinculado à conta.
  • O replay não valida assinatura, por desenho. Só quem tem WPP_BIZ_WEBHOOKS_WRITE deve receber essa permissão.
  • O replay de eventos de mensagem hoje não reprocessa. O log guarda o value do evento, não o envelope completo que o reprocessador espera — a chamada devolve sucesso sem fazer nada. Ver §15. Para reconstituir uma mensagem perdida, o caminho é o payload cru do passo 2.

Encaminhar mensagens recebidas para o seu sistema

Objetivo: fazer o WhatsApp alimentar a sua aplicação sem escrever um relay.

# Modo BASIC — todos os textos e imagens recebidos de um número específico
curl -s -X POST "$BASE/dispatchers" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"name\": \"Esteira de atendimento\",
    \"mode\": \"BASIC\",
    \"messageTypes\": [\"TEXT\", \"IMAGE\"],
    \"phoneNumberId\": \"$PHONE\",
    \"endpointUrl\": \"https://seu-sistema.com.br/hooks/whatsapp\",
    \"signingSecret\": \"um-segredo-com-pelo-menos-16-caracteres\",
    \"timeoutMs\": 15000,
    \"retryConfig\": {\"maxRetries\": 5, \"retryBackoffMs\": 1000, \"retryBackoffMultiplier\": 2}
  }" | jq '.data | {id, subscriptionId, status}'
# Modo ADVANCED — só interativos cujo payload de botão comece com "PROPOSTA_"
curl -s -X POST "$BASE/dispatchers" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Respostas de proposta",
    "mode": "ADVANCED",
    "events": ["wpp-biz.message.received.interactive"],
    "endpointUrl": "https://seu-sistema.com.br/hooks/proposta",
    "conditions": {
      "match": "all",
      "filters": [
        { "path": "$.text", "operator": "starts_with", "value": "PROPOSTA_" }
      ]
    }
  }' | jq '.data.id'
# Acompanhar a entrega e reprocessar o que falhou
curl -s "$BASE/dispatchers/$DISP/delivery-logs?status=failed" -H "Authorization: Bearer $TOKEN" | jq
curl -s -X POST "$BASE/dispatchers/$DISP/delivery-logs/$LOG/retry" -H "Authorization: Bearer $TOKEN" | jq

Armadilhas.

  • messageTypes e events se combinam, não se substituem. Passar os dois gera a união dos filtros. Se nenhum for informado em modo BASIC, o padrão é wpp-biz.message.received.*.
  • Guarde o signingSecret. Ele é cifrado no banco e não volta em nenhuma leitura. Perdeu, atualize o dispatcher com um novo e ajuste o seu verificador.
  • Excluir o dispatcher exclui a inscrição no Webhooks Engine. Não há órfão para limpar, mas também não há como recuperar o histórico depois.

Descobrir por que a mensagem não chegou

Objetivo: o roteiro de diagnóstico, na ordem que resolve mais rápido.

# 1. O que o BB registrou sobre a mensagem
curl -s "$BASE/messages/$MSG_ID" -H "Authorization: Bearer $TOKEN" \
  | jq '{status, errorCode, errorMessage, sentAt, deliveredAt, readAt}'

# 2. A janela estava aberta? (só importa para POST /send)
curl -s "$BASE/contacts/$CONTACT/conversation-window?phoneNumberId=$PHONE" \
  -H "Authorization: Bearer $TOKEN" | jq

# 3. O número está saudável na Meta?
curl -s "$BASE/accounts/phone-numbers/$META_PHONE_ID/status" -H "Authorization: Bearer $TOKEN" | jq

# 4. A WABA está aprovada?
curl -s "$BASE/accounts/$ACCOUNT/waba-info" -H "Authorization: Bearer $TOKEN" | jq

# 5. O template está mesmo aprovado na Meta (e não só no cache local)?
curl -s "$BASE/accounts/$ACCOUNT/templates?status=APPROVED" -H "Authorization: Bearer $TOKEN" | jq

Ordem de causa mais comum: janela fechada em POST /send · template não aprovado ou nome com typo · parâmetro faltando nos components · token da Meta expirado (META_TOKEN_EXPIRED) · qualidade do número rebaixada ou limite de envio atingido (META_RATE_LIMIT) · destinatário sem WhatsApp naquele número.

Rotacionar o token da Meta sem derrubar o envio

Objetivo: trocar o access token de uma conta com o mínimo de janela de erro.

curl -s -X PATCH "$BASE/accounts/$ACCOUNT" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"accessToken":"EAA_NOVO_TOKEN"}' \
  | jq '.data | {id, hasAccessToken}'

# Validar imediatamente com uma leitura que toca a Meta
curl -s "$BASE/accounts/$ACCOUNT/waba-info" -H "Authorization: Bearer $TOKEN" | jq '.data'

Armadilhas.

  • O token novo tem que vir do mesmo Meta App. O BB valida com debug_token e recusa se o app do token não bater com o WppBizApp vinculado.
  • Token de system user permanente ainda pode ser revogado. Monitore META_TOKEN_REVOKED nos logs, não só a expiração.
  • A troca não reconfigura o webhook. A assinatura na Meta é do app, não do token — mas vale conferir com GET /accounts/:id/webhooks depois de qualquer mexida.

Colocar um atendente humano na conversa

Objetivo: sair da automação e entregar a conversa a uma pessoa.

# 1. Registrar o agente a partir de um usuário do IAM
AGENT=$(curl -s -X POST "$BASE/agents" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"userId":"UUID_DO_USUARIO_NO_IAM","name":"Ana Souza","maxConcurrent":8,"skills":["cobranca"]}' \
  | jq -r '.data.id')

curl -s -X PATCH "$BASE/agents/$AGENT/status" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"status":"AVAILABLE"}' | jq '.data.status'

# 2. Atribuir a conversa (sem agentId, o BB escolhe o de menor carga)
curl -s -X POST "$BASE/inbox/assign" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{
    \"contactPhone\": \"5511999998888\",
    \"phoneNumberId\": \"$PHONE\",
    \"priority\": \"HIGH\",
    \"subject\": \"Negociação de parcela\",
    \"slaMinutes\": 30
  }" | jq '.data | {id, agentId, status, slaDeadlineAt}'

# 3. Transferir e encerrar
curl -s -X POST "$BASE/inbox/$ASSIGN/transfer" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"toAgentId":"UUID_DO_OUTRO_AGENTE","reason":"especialista em cobrança"}' | jq

curl -s -X POST "$BASE/inbox/$ASSIGN/resolve" -H "Authorization: Bearer $TOKEN" | jq '.data.status'

Armadilhas.

  • Mensagem recebida não cria atribuição. Quem chama POST /inbox/assign é a sua aplicação, tipicamente ao consumir o evento wpp-biz.message.received.* (§15).
  • slaDeadlineAt é gravado, mas ninguém vigia. Não há job que dispare alerta de SLA estourado. Se você precisa disso, monte a verificação do seu lado.
  • skills e conditions das regras não influenciam a distribuição. A escolha automática é sempre o agente disponível de menor carga (§15).
  • POST /inbox/:id/notes hoje não funciona com token JWT. A rota resolve o agente por um campo que o token não carrega e responde 403. Registre a nota no seu sistema até a correção (§15).

12Integração com outros building blocks

Building blockComo se relacionaObrigatório
IAMEmite o token, define o organizationId que isola cada WABA e o vocabulário WPP_BIZ_* de permissõesSim
Webhooks EngineCada dispatcher é uma inscrição lá; entrega, retentativa e log de entrega são deleSim, para dispatchers
API KeysAlternativa ao JWT: X-API-Key ou Authorization: ApiKey <chave>, com permissão por padrão de recursoNão
Payments (src/payments)Processa o PIX ou o boleto de verdade; o WPP Business só entrega a mensagem de cobrançaNão
CommerceDono do catálogo, do pedido e do estoque de verdade; o WhatsApp é a vitrine e o canalNão
CustomersCadastro oficial do cliente; o WppBizContact é o identificador de conversa, não o cadastroNão
Audit TrailRegistra quem enviou o quê para quem, quando a operação é reguladaNão
AI Engine (src/ai-engine)Consome o evento de mensagem recebida e devolve a resposta por POST /messages/send — é assim que se monta atendimento com IA hojeNão
   ┌──────────┐  login / client_credentials   ┌──────────────────────────────┐
   │   IAM    │ ────────────────────────────▶ │  token com organizationId    │
   └──────────┘                               │  e permissões WPP_BIZ_*      │
                                              └───────────────┬──────────────┘
                                                              │
   ┌──────────┐    webhook assinado (HMAC)    ┌───────────────▼──────────────┐
   │   Meta   │ ────────────────────────────▶ │       WPP BUSINESS           │
   │ Cloud API│ ◀──────────────────────────── │  contatos · mensagens ·      │
   └──────────┘   envio via Graph API v25.0   │  templates · campanhas ·     │
                                              │  inbox · dispatchers         │
                                              └───┬──────────┬───────────┬───┘
                                                  │          │           │
                          evento wpp-biz.*        │          │           │ cobrança
                                                  ▼          ▼           ▼
                                        ┌──────────────┐ ┌────────┐ ┌──────────┐
                                        │  Webhooks    │ │   AI   │ │ Payments │
                                        │   Engine     │ │ Engine │ │          │
                                        └──────┬───────┘ └────┬───┘ └──────────┘
                                               │              │
                                    entrega    ▼              ▼ resposta gerada
                                   ┌───────────────────┐   volta por
                                   │ seus sistemas /   │   POST /messages/send
                                   │ Decision Platform │
                                   └───────────────────┘

O argumento é este: o WhatsApp não vira uma ilha. A mensagem que o cliente manda entra na mesma malha de eventos que já move proposta, pagamento e pedido — com o mesmo token, o mesmo tenant e o mesmo mecanismo de entrega.


13Configuração e operação

Variáveis de ambiente

VariávelDescriçãoObrigatóriaPadrão
WPP_BUSINESS_CREDENTIAL_MASTER_KEYChave AES-256-GCM, 64 caracteres hexadecimais (32 bytes). Cifra accessToken, appSecret e signingSecret. Gere com openssl rand -hex 32.Na prática, sim — o schema a marca como opcional, mas qualquer operação com segredo falha sem ela
WPP_BUSINESS_WEBHOOK_BASE_URLURL pública do serviço. A callback registrada na Meta é <base>/wpp-business/api/v1/webhook.Para configurar webhook
MODULE_WPP_BUSINESS_PORTPorta no modo standaloneNão3025
MODULE_WPP_BUSINESS_URLURL do módulo para chamadas entre building blocksNão''
DATABASE_URLPostgreSQL. O BB usa o schema wpp_business.Sim
JWT_SECRETVerificação do token do IAMSim
DEPLOYMENT_MODEmonolith ou standaloneNãomonolith

Não existem META_APP_ID, META_APP_SECRET nem versão da Graph API em variável de ambiente — credencial da Meta é por tenant, no banco, e a versão da Graph API (v25.0) é fixa no código.

Em produção, mudar variável de ambiente exige deploy completo da stack. Subir só a imagem do serviço não propaga variável nova — o tutorial de webhook documenta esse detalhe, que já custou uma investigação.

Dependências de infraestrutura

DependênciaPara quê
PostgreSQLSchema wpp_business, 27 tabelas
Meta Graph API v25.0Todo envio, template, mídia, flow e consulta de estado
Webhooks EngineEntrega dos eventos configurados por dispatcher
IAMVerificação de token e vocabulário de permissões

Limites e quotas

LimiteValorOrigem
Importação em massa de contatos1.000 por chamadaSchema Zod do BB
Lote de envio de campanha50 mensagens, com 1 segundo entre lotesImplementação do BB
Retentativas no cliente da Meta3 tentativas, backoff 1s → 2sImplementação do BB
Timeout da chamada à MetaNão há — ver §15
Corpo de mensagem interativa1.024 caracteresCloud API
Rodapé de mensagem interativa60 caracteresCloud API
Seções em lista de produtos10Cloud API
Tipos de mídia aceitos no uploadJPEG, PNG, MP4, AAC/MP4/MPEG/OGG, PDF, OfficeSchema Zod do BB

Limites da própria Meta — não são do BB, mas são eles que derrubam a operação na prática. Todos da documentação oficial, consultada em 2026-08-16.

LimiteValorFonte
Tiers de envio (usuários únicos por 24h)250 → 2.000 → 10.000 → 100.000 → ilimitadoMessaging Limits
Como sair de 250Verificação de negócio, verificação via parceiro, ou 2.000 mensagens entregues fora da janela em 30 dias com template de alta qualidadeidem
Escalonamento automáticoEm até 6 horas, se a qualidade estiver alta e você tiver usado ao menos metade do limite atual nos últimos 7 diasidem
Throughput por número80 mensagens por segundo, com upgrade disponívelCloud API Overview
Mesmo destinatário1 mensagem a cada 6 segundos (rajada de até 45 em 6s, com espera proporcional)idem
Requisições de API200 por hora por app e WABA; 5.000 por hora para WABAs ativasidem
Janela de entrada gratuita (anúncio Click-to-WhatsApp)72 horas, com qualquer tipo de mensagem sem custoPricing

O BB não impõe limitação de taxa própria e não conhece o seu tier. Uma campanha em lotes de 50 por segundo cabe folgadamente nos 80 msg/s, mas estourar o tier de usuários únicos por 24h derruba a qualidade do número — e qualidade baixa leva a rebaixamento de tier.

Vocabulário de permissões — 31 strings WPP_BIZ_* declaradas no IAM:

WPP_BIZ_READ · WPP_BIZ_ACCOUNTS_MANAGE · WPP_BIZ_MESSAGES_SEND · WPP_BIZ_MESSAGES_READ · WPP_BIZ_CONTACTS_READ · WPP_BIZ_CONTACTS_WRITE · WPP_BIZ_TEMPLATES_READ · WPP_BIZ_TEMPLATES_WRITE · WPP_BIZ_CAMPAIGNS_READ · WPP_BIZ_CAMPAIGNS_WRITE · WPP_BIZ_CAMPAIGNS_EXECUTE · WPP_BIZ_AUTOMATIONS_READ · WPP_BIZ_AUTOMATIONS_WRITE · WPP_BIZ_DISPATCHERS_READ · WPP_BIZ_DISPATCHERS_WRITE · WPP_BIZ_WEBHOOKS_READ · WPP_BIZ_WEBHOOKS_WRITE · WPP_BIZ_FLOWS_MANAGE · WPP_BIZ_MEDIA_READ · WPP_BIZ_MEDIA_MANAGE · WPP_BIZ_CATALOG_READ · WPP_BIZ_CATALOG_MANAGE · WPP_BIZ_ORDERS_READ · WPP_BIZ_ORDERS_MANAGE · WPP_BIZ_ANALYTICS_READ · WPP_BIZ_SHORT_LINKS_MANAGE · WPP_BIZ_AGENTS_MANAGE · WPP_BIZ_INBOX_MANAGE · WPP_BIZ_PAYMENTS_READ · WPP_BIZ_PAYMENTS_MANAGE · WPP_BIZ_ADMIN

WPP_BIZ_ADMIN existe no vocabulário mas nenhuma rota a exige — conceda as permissões específicas.

Catálogo de erros

Statuscode / details.codeSignificaO que fazer
400VALIDATIONCorpo reprovado no ZodConfira campos e tipos contra a §9
400BAD_REQUESTPré-condição do BB não atendida (conta sem app, sem verify token, WPP_BUSINESS_WEBHOOK_BASE_URL ausente)A mensagem diz o que falta
400APP_NOT_REGISTEREDO token da Meta pertence a um app não registrado; o appId vem na respostaPOST /apps com esse appId
400META_WABA_ID_REQUIREDNão deu para descobrir a WABA a partir do númeroMande wabaId no corpo
400META_API_ERRORA Meta recusou a chamada (template inválido, janela fechada, parâmetro faltando)Leia details.metaMessage e details.metaTraceId
401META_TOKEN_EXPIREDToken da Meta expirouPATCH /accounts/:id com token novo
401META_TOKEN_REVOKEDToken revogado pela Meta ou pelo administradorGere outro no Business Manager
401META_TOKEN_INVALIDToken inválidoConfira se copiou o token certo
401UNAUTHORIZEDToken do IAM ausente, inválido ou expirado; ou assinatura de webhook inválidaRenove o token; no webhook, confira o App Secret
403FORBIDDENFalta permissão WPP_BIZ_* ou organizationId no tokenConfira o token e as permissões contratadas
403META_INSUFFICIENT_SCOPEO token da Meta não tem o escopo exigidoRegere com whatsapp_business_messaging e whatsapp_business_management
404NOT_FOUNDRecurso inexistente, excluído logicamente ou de outra organizaçãoConfira o ID e o tenant
409CONFLICTappId, wabaId, telefone, etiqueta, retailerId ou agente já existentesEscolha outro identificador
429META_RATE_LIMITLimite de taxa da MetaRecue com backoff próprio — o BB não retenta 429
500INTERNALFalha no banco ou erro não classificado da MetaConsulte o log; se for da Meta, details.metaTraceId abre chamado

Observabilidade

  • GET /wpp-business/health devolve identificação e versão do serviço. É a sonda para orquestrador.
  • WppBizWebhookLog é o diário de bordo do que a Meta mandou — payload completo, cabeçalhos, IP de origem, tamanho do corpo, tempo de processamento e status (PROCESSED, FAILED, IGNORED). GET /webhook/logs/stats resume.
  • Todo erro da Meta carrega fbtrace_id em details.metaTraceId. É o identificador que o suporte da Meta pede.
  • Os eventos publicados são o sinal em tempo real. Além de wpp-biz.message.received e wpp-biz.message.status.updated, há eventos granulares por tipo e por status, além de wpp-biz.campaign.started/completed, wpp-biz.template.status.changed, wpp-biz.account.alert, wpp-biz.phone.quality.updated e wpp-biz.conversation.*.
  • wpp-biz.phone.quality.updated e wpp-biz.account.alert são os que valem alarme. Qualidade rebaixada e alerta de conta antecedem a perda do canal.

14Segurança e compliance

Isolamento entre tenants. Toda rota autenticada aplica requireOrganization e lê o organizationId do claim assinado no JWT — nenhuma rota lê a organização do corpo da requisição. Os repositórios filtram por organizationId em toda consulta, e as tabelas têm chave estrangeira para Organization com onDelete: Cascade. Há teste de regressão dedicado a isso em tests/integration/wpp-business/regression-tenant-isolation.integration.test.ts.

No webhook, o isolamento vem do payload da Meta. O endpoint público não tem token; a organização é derivada do wabaId que a Meta envia, e a assinatura HMAC é validada com o App Secret daquela conta específica. Uma organização não consegue forjar evento para outra sem possuir o App Secret dela.

Segredos em repouso. WppBizAccount.accessToken, WppBizApp.appSecret, WppBizAccount.appSecret (legado) e WppBizDispatcher.signingSecret são cifrados com AES-256-GCM (iv:authTag:ciphertext), com chave em WPP_BUSINESS_CREDENTIAL_MASTER_KEY gerida por SOPS. A cifragem acontece na borda do repositório ou do serviço, e nenhum desses campos é devolvido pela API — as respostas trazem hasAccessToken e hasAppSecret como booleanos.

Verificação de assinatura em tempo constante. O HMAC-SHA256 do webhook é comparado com timingSafeEqual, com checagem de comprimento antes.

Autenticação e permissões. Bearer JWT do IAM ou API Key (X-API-Key ou Authorization: ApiKey <chave>). Com JWT, vale o RBAC das permissões WPP_BIZ_*; com API Key, vale o padrão de recurso configurado na chave. Toda negação de permissão gera evento no log de segurança com IP, recurso e ação.

Dado pessoal e LGPD. O BB guarda telefone, nome, e-mail e o conteúdo das mensagens — tudo dado pessoal, e conversa de atendimento costuma conter mais do que se espera. Pontos de atenção:

  • WppBizContact tem exclusão lógica (deletedAt); WppBizMessage não tem — a mensagem sobrevive à exclusão do contato, com contactId virando nulo.
  • WppBizWebhookLog guarda o payload cru da Meta, o que inclui o texto da mensagem, e não tem política de expurgo automática (§15). Defina retenção operacional.
  • Atender a pedido de eliminação exige processo explícito de expurgo, hoje não automatizado.

Base legal para mensagem ativa. Enviar template de MARKETING exige opt-in do titular, tanto pela LGPD quanto pela própria política da Meta. O BB registra etiqueta e metadado no contato, mas não gerencia consentimento — a prova do opt-in é responsabilidade da sua aplicação, e é a primeira coisa que a Meta pede quando a qualidade do número cai por denúncia.

Automação exige saída para humano. A política de mensageria da Meta permite responder de forma automática dentro da janela de 24 horas, mas com uma condição explícita: "You may use automation when responding during the 24-hour window, but must also have available prompt, clear, and direct escalation paths" (WhatsApp Business Messaging Policy, consultado em 2026-08-16). Na prática, uma automação por palavra-chave sem caminho para atendente é descumprimento de política, não só experiência ruim — e vale notar que 59% dos brasileiros dizem não gostar de respostas automáticas e preferir falar com humano (Opinion Box, Pesquisa WhatsApp no Brasil, edição 2025, 1.126 respondentes, campo em junho de 2025 — Opinion Box). A caixa de entrada com agentes deste BB existe para ser esse caminho.

Canal oficial como controle. Usar a Cloud API em vez de automação não oficial do WhatsApp Web não é preferência técnica: é o que mantém a operação dentro dos Termos de Serviço do WhatsApp Business e evita banimento do número. Para operação regulada, é a diferença entre ter e não ter o canal.


15Limitações conhecidas

Esta seção é longa de propósito. O BB é grande, e parte da superfície está mais madura que a outra.

Frentes que não estão prontas para produção

LimitaçãoImpactoSituação
Catálogo não sincroniza com o Commerce ManagerWppBizCatalog.metaCatalogId nunca é preenchido por nenhum endpoint, e não existe rota de conexão. Consequência: POST /catalogs/send-product e /send-product-list sempre falham. Os produtos locais são um espelho que não existe na Meta.Não implementado — use o Commerce como catálogo e o Commerce Manager da Meta para a vitrine
Não há como criar pedido pela APIWppBizOrder não tem POST, e o webhook não interpreta mensagem do tipo order — um pedido do carrinho do WhatsApp chega com conteúdo vazio e se perde. Como POST /payments exige um orderId, o fluxo de cobrança fica inalcançável pela API.Não implementado
Nenhum gateway de pagamento está integradosend-pix e send-boleto montam a mensagem order_details da Meta e mandam; o processamento é do PSP configurado no Commerce Manager. WppBizPaymentConfig.provider, credentials e pixKey são gravados e nunca lidos por lógica nenhuma. pixQrCode, boletoBarcode e boletoUrl nunca são preenchidos.Por desenho parcial — guarde credencial de gateway no Payments (src/payments), não aqui
PIX e boleto dependem de habilitação da MetaO código envia payment_gateway.type como br_pix_offsite e br_boleto. Em teste real de 2026-03, a Meta v25 só aceitou gateways registrados (cielo, payu, razorpay, zaakpay, billdesk) sem o programa WhatsApp Payments Brasil habilitado na conta (registro do teste).Bloqueado por habilitação da Meta
Flow Data Endpoint não existeO BB cria, publica e envia flows, mas não hospeda o endpoint de dados — não há descriptografia RSA-OAEP, nem AES-128-GCM, nem tratamento de ping ou data_exchange. Um flow com endpointUri precisa ser servido por outro sistema.Não implementado
QR Code do link curto é um marcador, não um QRCom generateQr: true, qrCodeData recebe um SVG com o texto literal "QR Code" e a URL escrita por extenso. Não é escaneável. O shortUrl também não é encurtado — é um link wa.me montado, e clickCount só sobe se alguém chamar POST /short-links/:id/click.Não implementado

Comportamentos que surpreendem quem integra

LimitaçãoImpactoSituação
O webhook não persiste o estado vindo da MetaAprovação e rejeição de template, nota de qualidade do número, status de flow, alerta de conta e revisão de conta são publicados como evento mas não atualizam o banco. WppBizTemplate.status só muda com POST /templates/:id/sync. O estado local pode divergir silenciosamente da Meta.Roadmap
Campanha roda dentro do processo, sem filaSem BullMQ e sem worker: um restart ou deploy no meio de uma campanha deixa os destinatários restantes em PENDING para sempre. Não há retomada.Roadmap
scheduledAt não tem agendadorO campo é gravado e o status SCHEDULED é aceito, mas nada dispara a campanha na hora marcada. O disparo é sempre manual, por POST /campaigns/:id/execute.Não implementado
deliveredCount e readCount da campanha não sobemOs envios de campanha não gravam linha em wpp_biz_messages, então o status update da Meta não encontra o destinatário para atualizar os contadores. sentCount e failedCount são confiáveis; os outros dois, não.Roadmap
Filtro de campanha só entende tagIdsQualquer outra chave em filters é ignorada em silêncio e a campanha vai para toda a base de contatos da organização. Confira totalRecipients antes de confiar.Roadmap
Envio por automação, campanha e flow não entra no históricoPOST /messages/* grava em wpp_biz_messages. Resposta automática, disparo de campanha e mensagem de flow não aparecem em GET /messages.Roadmap
POST /webhook/logs/:id/replay não reprocessa mensagensO log guarda o value do evento, e o reprocessador espera o envelope completo ({object, entry}). A chamada devolve sucesso e não faz nada.Bug conhecido
POST /inbox/:id/notes responde 403 com token JWTA rota resolve o agente por um campo de usuário que o token não carrega, e nunca encontra o agente.Bug conhecido
Tipos de mensagem recebida não cobertosorder, button (resposta rápida de template), system e referral (anúncio Click-to-WhatsApp) não são mapeados. Chegam com conteúdo vazio, e tipos fora do enum podem falhar na gravação.Roadmap
WppBizWebhookLog cresce sem expurgoNão há job de retenção. A tabela guarda payload e cabeçalhos completos indefinidamente. Monte expurgo operacional.Roadmap

Atendimento humano — o que existe e o que não existe

LimitaçãoImpactoSituação
Mensagem recebida não abre nem roteia atendimentoNada no webhook chama a caixa de entrada. A atribuição só nasce de POST /inbox/assign, chamado pela sua aplicação.Por desenho hoje; roadmap para automático
Regras de distribuição ignoram conditions, priority e skillsQualquer regra habilitada casa com tudo, a ordem não respeita prioridade, e habilidade do agente não influencia nada.Roadmap
round_robin não faz rodízioEscolhe o primeiro agente disponível, igual a least_loaded. Sem estado de rodízio, o mesmo agente recebe tudo até estourar a capacidade.Roadmap
assign_team não tem implementaçãoA regra é aceita e cai no comportamento padrão, em silêncio.Não implementado
SLA é gravado e nunca verificadoslaDeadlineAt é calculado, mas não há job, rota ou evento de SLA estourado. firstResponseAt nunca é preenchido.Roadmap
WppBizConversationTransfer é tabela órfãAs transferências ficam registradas no metadata da atribuição; a tabela dedicada não recebe linha.Roadmap
currentLoad pode ficar infladoReatribuir a mesma conversa ao mesmo agente incrementa a carga de novo, e não há reconciliação periódica.Bug conhecido

Robustez do cliente da Meta

LimitaçãoImpactoSituação
Não há timeout nas chamadas à Graph APIUma chamada pendurada na Meta segura a requisição do cliente indefinidamente.Roadmap
Erro 400 permanente é retentado três vezesFalhas definitivas (template inválido, parâmetro faltando) sobem classificadas como internas e passam pelo retry, gastando ~3 segundos por chamada antes de falhar.Bug conhecido
429 não tem backoffLimite de taxa não é retentado nem respeita Retry-After; a chamada falha com META_RATE_LIMIT e o recuo é responsabilidade de quem chamou. Não há limitador de taxa no BB.Por desenho parcial
Upload e download de mídia e algumas chamadas de webhook não têm retryEssas rotas não passam pelo caminho com retentativa e devolvem erro genérico, sem details.code da Meta.Roadmap
A versão da Graph API é fixa no códigoSubir de v25.0 exige deploy, não configuração.Por desenho

Fora do escopo, por desenho

Não fazOnde está
Bot com IA em linguagem natural — a automação aqui casa palavra-chave e executa só a primeira regra que casarAI Engine (src/ai-engine) consumindo o evento e respondendo por POST /messages/send
Construtor visual de fluxo conversacionalWhatsApp Flows da Meta, editado por JSON
Conexão não oficial via WhatsApp Web (Baileys, whatsapp-web.js)Building block wpp
SMS, voz, pushFora do catálogo hoje; e-mail está em building block próprio
Gestão de consentimento e opt-inSua aplicação
Analytics de operação (tempo de resposta, produtividade de agente, funil de campanha agregado)Só existe agregação por categoria e origem de conversa

16Perguntas frequentes

Por que a minha mensagem de texto não chega, mas o template chega?

Porque a janela de 24 horas está fechada. Fora dela, o WhatsApp só entrega template aprovado. A janela abre quando o contato te escreve e dura 24 horas a partir daí. Consulte GET /contacts/:id/conversation-window?phoneNumberId=<uuid> antes de escolher entre POST /messages/send e POST /messages/send-template — é o erro de integração número um deste BB.

Quanto tempo a Meta demora para aprovar um template?

Não temos SLA nosso para prometer, porque quem aprova é a Meta. O que a gente controla é o depois: POST /templates/:id/sync traz o status atual e o rejectionReason quando há.

O que mais importa saber é outra coisa: a Meta recategoriza sem rejeitar. Desde abril de 2025, se você pede UTILITY e ela entende que é MARKETING, o template é aprovado como MARKETING (Template Categorization, consultado em 2026-08-16). Como o MARKETING custa cerca de nove vezes o UTILITY no Brasil, a sua régua de aviso transacional pode ficar muito mais cara sem que nada quebre. Confira sempre a categoria efetiva em GET /accounts/:id/templates.

Posso usar um número que já tem WhatsApp comum ou WhatsApp Business no celular?

Não sem migrar. Um número na Cloud API deixa de funcionar no aplicativo — é um ou outro. Na prática, quase toda operação usa um número novo, e é o caminho recomendado: assim você testa sem derrubar o atendimento que já roda.

Preciso de um Meta App por cliente?

Não necessariamente. O modelo suporta um WppBizApp por organização e várias WABAs por app. O que não dá é usar um token emitido por um app que a organização não registrou em POST /apps — o BB valida isso com debug_token e recusa com APP_NOT_REGISTERED, justamente para não misturar credenciais entre tenants.

Dá para colocar IA respondendo as conversas?

Dá, mas não é este BB que faz isso. A automação aqui casa palavra-chave e responde texto ou template. O caminho é um dispatcher assinando wpp-biz.message.received.text, o AI Engine (src/ai-engine) gerando a resposta e a sua aplicação devolvendo por POST /messages/send. Se o seu requisito principal é bot conversacional pronto, releia a §5 — a Blip e a Gupshup entregam isso de caixa.

Como funciona a cobrança do WhatsApp?

Quem cobra é a Meta, por mensagem de template entregue — o modelo por conversa de 24 horas acabou em 1º de julho de 2025. O preço varia por categoria (MARKETING, UTILITY, AUTHENTICATION) e por país do destinatário. Hoje, responder dentro de uma janela de atendimento aberta é gratuito. O BB registra cada conversa em wpp_biz_conversations com a categoria e o modelo de preço que a própria Meta informa no status update, e GET /analytics/conversations/stats agrupa isso — é o que permite ratear a fatura por cliente.

Duas datas para colocar no calendário: 1º de outubro de 2026, quando a Meta passa a cobrar também pelas respostas dentro da janela de 24 horas (no Brasil, o valor noticiado é R$ 0,035 por mensagem), e 30 de junho de 2027, prazo final para migrar a WABA para faturamento em reais, sob pena de a Meta parar de entregar as mensagens. A precificação da camada Catalisa está em definição (§6). Baixe a tabela oficial da sua conta no WhatsApp Manager antes de fechar número em proposta — a página pública não expõe os valores por país.

Qual a diferença entre o wpp e o wpp-business?

O wpp-business fala com a Cloud API oficial da Meta: exige verificação de negócio, template aprovado e respeita a janela de 24h, mas tem SLA, suporte e não corre risco de banimento por uso não oficial. O building block wpp usa conexão não oficial via WhatsApp Web, que dispensa toda a burocracia e serve para prototipar ou para uso interno — ao custo de violar os Termos de Serviço do WhatsApp Business. Para operação regulada ou cliente final, o caminho é o wpp-business.

O que acontece se o token da Meta expirar de madrugada?

Os envios passam a falhar com 401 e details.code igual a META_TOKEN_EXPIRED ou META_TOKEN_REVOKED — a distinção existe justamente para o alarme saber se é expiração ou revogação. O conserto é PATCH /accounts/:id com o token novo, que precisa vir do mesmo Meta App. A recomendação é usar token de system user permanente e monitorar o evento wpp-biz.account.alert.

Consigo mandar campanha para toda a minha base de uma vez?

Consegue, e é justamente por isso que dá para se machucar. O único filtro implementado é tagIds; qualquer outro é ignorado em silêncio e a campanha vai para todos os contatos da organização. Sempre etiquete o público e confira totalRecipients logo após o execute.

Some a isso o limite da Meta, que quase ninguém confere antes: um número novo começa em 250 usuários únicos por 24 horas e sobe para 2.000, 10.000, 100.000 e ilimitado conforme a qualidade e o histórico (Messaging Limits, consultado em 2026-08-16). Disparar acima do tier não é só ineficaz — derruba a avaliação de qualidade do número e, na sequência, o próprio tier.

Posso guardar as conversas para sempre?

Tecnicamente sim, e é aí que mora o problema. Mensagem contém dado pessoal, e o WppBizWebhookLog guarda o payload cru da Meta — com o texto da mensagem dentro — sem expurgo automático (§15). Defina retenção operacional para as duas tabelas antes de colocar volume, e trate pedido de eliminação como processo explícito.

O building block é multi-tenant de verdade ou é uma instalação por cliente?

De verdade. Cada empresa cliente é uma Organization do IAM, com WppBizApp e WppBizAccount próprios e segredos cifrados individualmente. O webhook único da Meta é roteado pelo wabaId do payload até a conta certa, e a assinatura é validada com o App Secret daquela conta. Há teste de regressão dedicado ao isolamento entre tenants.


Aprofundamentos. Guias longos por recurso vivem em `docs/wpp-business/` — os mais úteis são accounts, messages, dispatchers, payments e setup-real-device, além do tutorial de configuração de webhook.

⚠️ Este README é a fonte de verdade. O docs/wpp-business/README.md está desatualizado: ele descreve o building block wpp (multi-driver com Baileys, fila BullMQ, prefixo /wpp/api/v1, respostas 202 com correlationId), e nada daquilo se aplica aqui. Em qualquer divergência entre docs/ e este arquivo, vale este arquivo.


Padrão: PADRAO-DOCUMENTACAO.md · Índice: INDEX.md · Credenciais de ambiente: AMBIENTES.md

Building blocks relacionados