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.
- 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
- 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
- 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.
| Atributo | Valor |
|---|---|
| Identificador | wpp-business |
| Categoria | Comunicação |
| Escopo | Tenant (exige organizationId no token em todas as rotas, exceto o webhook da Meta) |
| Porta (standalone) | 3025 |
| Path alias | @wpp-business |
| Prefixo HTTP | /wpp-business |
| Status | Produção desde 2026-03 |
| Depende de | PostgreSQL (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
| Antes | Depois |
|---|---|
| Um conjunto de credenciais Meta por cliente, guardado à mão | WppBizApp e WppBizAccount por organização, com segredo cifrado em AES-256-GCM |
| Um webhook da Meta chegando misturado para todos os clientes | O 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 resposta | GET /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 retentativa | Dispatchers com filtro por tipo, condição JSONPath, assinatura HMAC, backoff e log de entrega |
| Base de contatos duplicada num fornecedor externo | Contatos, 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ério | Catalisa WPP Business | Cloud API direta (Meta) | Twilio | 360dialog | Take Blip |
|---|---|---|---|---|---|
| Custo acima da tarifa da Meta | Precificação em definição (§6) | Nenhum | US$ 0,005 por mensagem, entrada e saída | €49–249/mês por número, sem margem por mensagem | Por conversa (R$ 1,25–1,40 adicional) e por agente (R$ 100–150/mês) |
| Preço público e completo | Em definição | Sim | Sim | Sim | Parcial — base não publicada |
| Cobra também na mensagem recebida | Não | Não | Sim | Não | Modelo de conversa |
| Multi-tenant nativo (várias WABAs isoladas) | Sim, por organizationId no token | Você constrói | Por subconta, você organiza | Por chave de API, você organiza | Modelo de contrato, não de API |
| Templates, campanha e contatos inclusos | Sim | Não | Produto separado | Não | Sim |
| Caixa de entrada com agentes | Sim, no mesmo BB, sem cobrança por assento | Não | Produto separado (Flex) | Não | Sim, cobrado por assento |
| Construtor visual de fluxo | Não (ver §15) | Não | Studio | Não | Sim, é o forte deles |
| Relay de eventos com retentativa e log | Sim, via Webhooks Engine | Você constrói | Sim | Parcial | Sim |
| Bot com IA | Não (ver §15) | Não | Sim, integrável | Não | Sim |
| SMS, voz e e-mail no mesmo contrato | Só e-mail, em BB separado | Não | Sim, é o forte deles | Não | Sim |
| Operação, nota fiscal e suporte no Brasil | Sim | Não | Parceiros | Não | Sim, é o forte deles |
| Dado do cliente fora da sua plataforma | Não sai | Não sai | Sai | Sai | Sai |
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
- A organização do IAM é a unidade de isolamento, não a instalação. Cada empresa cliente tem
WppBizAppeWppBizAccountpróprios, com token e App Secret cifrados, e o webhook único da Meta é roteado pelowabaIddo 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. - 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.
- O erro da Meta chega classificado. Token expirado, token revogado, escopo insuficiente e limite de taxa vêm em
details.codecom ofbtrace_idjunto. 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. - 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
MARKETINGno Brasil fica na ordem de US$ 0,0625 por mensagem e oUTILITY, 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 deAUTHENTICATIONdiverge 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
| Data | O que muda | Fonte |
|---|---|---|
| 1º/10/2026 | A 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/2027 | Prazo 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.
| Driver | Por 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 atendimento | Gratuitas hoje; passam a ser cobradas a partir de 1º/10/2026 |
| Conversas registradas | O 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 conectados | Cada número tem limite de envio, avaliação de qualidade e custo de verificação próprios |
| Eventos entregues por dispatcher | Entrega, 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 Business | Cloud API direta | 360dialog | Twilio | Take Blip | |
|---|---|---|---|---|---|
| Tarifa da Meta | Repassada | Direta | Repassada sem margem | Repassada | Embutida no modelo de conversa |
| Custo da camada | Precificação em definição | Zero em licença | ~€49 a €249 por número, por mês | + US$ 0,005 por mensagem, entrada e saída | Base não publicada + R$ 1,25–1,40 por conversa adicional |
| Custo por atendente | Nenhum | — | — | Flex, produto separado | R$ 100–150 por agente, por mês |
| Ordem de grandeza da camada, no cenário | Em definição | R$ 0 em licença | Centenas a poucos milhares de reais | Ordem de US$ 1.000 só nas 200 mil de saída, mais o que entrar | Milhares de reais, com 12 assentos |
| Engenharia para multi-tenant | Inclusa | 4 a 8 semanas, estimativa interna | Fora do escopo | Fora do escopo | Inclusa |
| Engenharia para relay de eventos | Inclusa | 2 a 4 semanas, estimativa interna | Parcial | Inclusa | Inclusa |
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
200mesmo 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 devolve401. O erro real fica noWppBizWebhookLog, com o payload cru guardado para reprocessar emPOST /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.
AccountRepositorycifra 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.decryptSecretIfEncrypteddeixa 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. 429da 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 comoMETA_RATE_LIMITpara 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
| Termo | Significa |
|---|---|
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. |
| Template | Mensagem pré-aprovada pela Meta. É o único jeito de iniciar conversa fora da janela de 24h. Tem categoria UTILITY, MARKETING ou AUTHENTICATION. |
| Janela de 24 horas | Perí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. |
| Dispatcher | Regra de encaminhamento de evento do WhatsApp para um endpoint externo. Vira uma inscrição no Webhooks Engine. |
| Automação | Resposta 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. |
| Flow | Formulário nativo do WhatsApp (WhatsApp Flows), definido por JSON e publicado na Meta. |
Modelo de dados — schema wpp_business no PostgreSQL, 27 modelos.
| Modelo Prisma | Tabela | Propósito | Campos-chave |
|---|---|---|---|
WppBizApp | wpp_biz_apps | Credenciais do Meta App | appId, appSecret (cifrado), único (organizationId, appId) |
WppBizAccount | wpp_biz_accounts | WABA da organização | wabaId, accessToken (cifrado), webhookVerifyToken, subscribedFields |
WppBizPhoneNumber | wpp_biz_phone_numbers | Número conectado | phoneNumberId (único global), displayNumber, qualityRating |
WppBizMessage | wpp_biz_messages | Histórico de mensagens | waMessageId, direction, status, type, sentAt/deliveredAt/readAt |
WppBizContact | wpp_biz_contacts | Contato do WhatsApp | phone, waId, único (organizationId, phone) |
WppBizContactTag | wpp_biz_contact_tags | Etiqueta de contato | name, color, único (organizationId, name) |
WppBizContactTagAssignment | wpp_biz_contact_tag_assignments | Contato ↔ etiqueta | Chave composta (contactId, tagId) |
WppBizTemplate | wpp_biz_templates | Cache local do template | name, language, category, status, metaTemplateId, rejectionReason |
WppBizCampaign | wpp_biz_campaigns | Disparo em massa | status, totalRecipients, sentCount, deliveredCount, readCount, failedCount |
WppBizCampaignRecipient | wpp_biz_campaign_recipients | Destinatário da campanha | status, waMessageId, único (campaignId, contactId) |
WppBizAutomation | wpp_biz_automations | Resposta automática | trigger, conditions, action, actionConfig, enabled |
WppBizWebhookLog | wpp_biz_webhook_logs | Log do webhook da Meta | payload, requestHeaders, sourceIp, processingTimeMs, status |
WppBizDispatcher | wpp_biz_dispatchers | Encaminhamento de evento | mode, events, messageTypes, endpointUrl, signingSecret (cifrado), subscriptionId |
WppBizFlow | wpp_biz_flows | WhatsApp Flow | metaFlowId, status, categories, jsonDefinition, endpointUri |
WppBizCatalog | wpp_biz_catalogs | Catálogo de produtos | metaCatalogId, isConnected — ver §15 |
WppBizProduct | wpp_biz_products | Produto do catálogo | retailerId, price, availability, único (catalogId, retailerId) |
WppBizOrder | wpp_biz_orders | Pedido feito no WhatsApp | waOrderId, status, totalAmount — ver §15 |
WppBizOrderItem | wpp_biz_order_items | Item do pedido | retailerId, quantity, unitPrice |
WppBizPaymentConfig | wpp_biz_payment_configs | Configuração de pagamento | provider, enabledMethods, pixKey — ver §15 |
WppBizPayment | wpp_biz_payments | Cobrança enviada | referenceId, method, status, amount, paymentLink |
WppBizConversation | wpp_biz_conversations | Conversa faturável da Meta | waConversationId, category, pricingModel, billable, expiresAt |
WppBizShortLink | wpp_biz_short_links | Link wa.me com mensagem pronta | shortUrl, prefilledMessage, clickCount, qrCodeData — ver §15 |
WppBizAgent | wpp_biz_agents | Atendente | userId (do IAM), status, maxConcurrent, currentLoad, skills |
WppBizConversationAssignment | wpp_biz_conversation_assignments | Conversa atribuída | agentId, status, priority, tags, slaDeadlineAt |
WppBizAgentNote | wpp_biz_agent_notes | Nota interna do atendimento | content, agentId |
WppBizConversationTransfer | wpp_biz_conversation_transfers | Transferência entre agentes | fromAgentId, toAgentId, reason — ver §15 |
WppBizRoutingRule | wpp_biz_routing_rules | Regra de distribuição | priority, conditions, action, actionConfig — ver §15 |
Enumerações principais
| Enum | Valores |
|---|---|
WppBizMessageDirection | INBOUND · OUTBOUND |
WppBizMessageStatus | PENDING · SENT · DELIVERED · READ · FAILED |
WppBizMessageType | TEXT · IMAGE · VIDEO · AUDIO · DOCUMENT · STICKER · LOCATION · CONTACTS · INTERACTIVE · TEMPLATE · REACTION |
WppBizTemplateStatus | DRAFT · PENDING · APPROVED · REJECTED |
WppBizTemplateCategory | UTILITY · MARKETING · AUTHENTICATION |
WppBizCampaignStatus | DRAFT · SCHEDULED · EXECUTING · PAUSED · COMPLETED · FAILED |
WppBizFlowStatus | DRAFT · PUBLISHED · DEPRECATED · BLOCKED · THROTTLED |
WppBizAgentStatus | AVAILABLE · BUSY · OFFLINE · AWAY |
WppBizConversationAssignmentStatus | OPEN · ASSIGNED · WAITING · RESOLVED · CLOSED |
WppBizPaymentStatus | PENDING · 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étodo | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/apps | Registra um Meta App e cifra o App Secret | WPP_BIZ_ACCOUNTS_MANAGE |
GET | /api/v1/apps | Lista os apps da organização | WPP_BIZ_READ |
GET | /api/v1/apps/:id | Busca um app | WPP_BIZ_READ |
PATCH | /api/v1/apps/:id | Atualiza nome ou App Secret | WPP_BIZ_ACCOUNTS_MANAGE |
DELETE | /api/v1/apps/:id | Exclusão lógica | WPP_BIZ_ACCOUNTS_MANAGE |
Contas WABA — /wpp-business/api/v1/accounts (27)
Conta
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/accounts | Cria a conta WABA (onboarding modo A) | WPP_BIZ_ACCOUNTS_MANAGE |
GET | /api/v1/accounts | Lista contas | WPP_BIZ_READ |
GET | /api/v1/accounts/:id | Busca conta | WPP_BIZ_READ |
PATCH | /api/v1/accounts/:id | Atualiza token, nome ou app vinculado | WPP_BIZ_ACCOUNTS_MANAGE |
DELETE | /api/v1/accounts/:id | Exclusão lógica | WPP_BIZ_ACCOUNTS_MANAGE |
Números da conta
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/accounts/:id/phone-numbers | Vincula um número à conta | WPP_BIZ_ACCOUNTS_MANAGE |
GET | /api/v1/accounts/:id/phone-numbers | Lista os números da conta | WPP_BIZ_READ |
Webhook na Meta
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/accounts/:id/webhooks | Consulta o estado da assinatura na Meta | WPP_BIZ_READ |
POST | /api/v1/accounts/:id/webhooks | Assina o app e a WABA nos campos escolhidos | WPP_BIZ_ACCOUNTS_MANAGE |
PUT | /api/v1/accounts/:id/webhooks | Troca os campos assinados | WPP_BIZ_ACCOUNTS_MANAGE |
DELETE | /api/v1/accounts/:id/webhooks | Remove a assinatura no nível do app | WPP_BIZ_ACCOUNTS_MANAGE |
POST | /api/v1/accounts/:id/regenerate-verify-token | Gera novo webhookVerifyToken | WPP_BIZ_ACCOUNTS_MANAGE |
Informação da WABA
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/accounts/:id/waba-info | Estado da WABA na Meta (verificação, revisão) | WPP_BIZ_READ |
Templates direto na Meta — a Meta é a fonte de verdade nestas rotas
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/accounts/:id/templates | Lista templates lendo a Meta | WPP_BIZ_TEMPLATES_READ |
POST | /api/v1/accounts/:id/templates | Cria o template direto na Meta | WPP_BIZ_TEMPLATES_WRITE |
DELETE | /api/v1/accounts/:id/templates | Apaga template na Meta por nome | WPP_BIZ_TEMPLATES_WRITE |
POST | /api/v1/accounts/:id/templates/:templateId/sync | Sincroniza um template local com a Meta | WPP_BIZ_TEMPLATES_WRITE |
PATCH | /api/v1/accounts/:id/templates/:templateId | Edita componentes ou categoria na Meta | WPP_BIZ_TEMPLATES_WRITE |
Operações de número — :phoneNumberId aqui é o ID da Meta, não o UUID interno
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/accounts/phone-numbers/:phoneNumberId/business-profile | Lê o perfil comercial | WPP_BIZ_ACCOUNTS_MANAGE |
PATCH | /api/v1/accounts/phone-numbers/:phoneNumberId/business-profile | Atualiza perfil comercial | WPP_BIZ_ACCOUNTS_MANAGE |
GET | /api/v1/accounts/phone-numbers/:phoneNumberId/status | Qualidade e estado do número | WPP_BIZ_READ |
POST | /api/v1/accounts/phone-numbers/:phoneNumberId/register | Registra o número na Cloud API com PIN | WPP_BIZ_ACCOUNTS_MANAGE |
POST | /api/v1/accounts/phone-numbers/:phoneNumberId/deregister | Cancela o registro | WPP_BIZ_ACCOUNTS_MANAGE |
POST | /api/v1/accounts/phone-numbers/:phoneNumberId/request-code | Pede código por SMS ou VOICE | WPP_BIZ_ACCOUNTS_MANAGE |
POST | /api/v1/accounts/phone-numbers/:phoneNumberId/verify-code | Confirma o código recebido | WPP_BIZ_ACCOUNTS_MANAGE |
POST | /api/v1/accounts/phone-numbers/:phoneNumberId/two-step-verification | Define o PIN de dois fatores | WPP_BIZ_ACCOUNTS_MANAGE |
PATCH | /api/v1/accounts/phone-numbers/:phoneNumberId/display-name | Pede troca do nome exibido | WPP_BIZ_ACCOUNTS_MANAGE |
Onboarding simplificado — /wpp-business/api/v1/devices (1)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/devices/standalone | Onboarding modo B: token + phoneNumberId + wabaId, o BB descobre o resto | WPP_BIZ_ACCOUNTS_MANAGE |
Mensagens — /wpp-business/api/v1/messages (7)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/messages/send | Texto, mídia, localização ou interativo (exige janela aberta) | WPP_BIZ_MESSAGES_SEND |
POST | /api/v1/messages/send-template | Template aprovado (abre a janela) | WPP_BIZ_MESSAGES_SEND |
POST | /api/v1/messages/send-interactive | Botões, lista, CTA de URL ou flow | WPP_BIZ_MESSAGES_SEND |
POST | /api/v1/messages/send-otp | Template de autenticação com código | WPP_BIZ_MESSAGES_SEND |
POST | /api/v1/messages/:id/mark-read | Marca a mensagem como lida na Meta | WPP_BIZ_MESSAGES_SEND |
GET | /api/v1/messages | Lista mensagens (phoneNumberId, contactId, limit, offset) | WPP_BIZ_MESSAGES_READ |
GET | /api/v1/messages/:id | Busca uma mensagem | WPP_BIZ_MESSAGES_READ |
Contatos — /wpp-business/api/v1/contacts (12)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/contacts | Cria contato | WPP_BIZ_CONTACTS_WRITE |
GET | /api/v1/contacts | Lista contatos | WPP_BIZ_CONTACTS_READ |
GET | /api/v1/contacts/search | Busca por texto, telefone, e-mail, etiqueta e período | WPP_BIZ_CONTACTS_READ |
POST | /api/v1/contacts/bulk | Importa até 1.000 contatos por chamada | WPP_BIZ_CONTACTS_WRITE |
GET | /api/v1/contacts/:id | Busca contato | WPP_BIZ_CONTACTS_READ |
PATCH | /api/v1/contacts/:id | Atualiza contato | WPP_BIZ_CONTACTS_WRITE |
DELETE | /api/v1/contacts/:id | Exclusão lógica | WPP_BIZ_CONTACTS_WRITE |
GET | /api/v1/contacts/:id/conversation-window | Estado da janela de 24h (exige ?phoneNumberId=) | WPP_BIZ_CONTACTS_READ |
POST | /api/v1/contacts/:id/tags | Atribui etiquetas ao contato | WPP_BIZ_CONTACTS_WRITE |
GET | /api/v1/contacts/tags | Lista etiquetas | WPP_BIZ_CONTACTS_READ |
POST | /api/v1/contacts/tags | Cria etiqueta | WPP_BIZ_CONTACTS_WRITE |
DELETE | /api/v1/contacts/tags/:tagId | Remove etiqueta | WPP_BIZ_CONTACTS_WRITE |
Templates (cache local) — /wpp-business/api/v1/templates (9)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/templates | Cria o template local como DRAFT | WPP_BIZ_TEMPLATES_WRITE |
GET | /api/v1/templates | Lista do cache local | WPP_BIZ_TEMPLATES_READ |
GET | /api/v1/templates/:id | Busca template local | WPP_BIZ_TEMPLATES_READ |
PATCH | /api/v1/templates/:id | Edita componentes ou categoria (bloqueado em PENDING) | WPP_BIZ_TEMPLATES_WRITE |
DELETE | /api/v1/templates/:id | Exclusão lógica | WPP_BIZ_TEMPLATES_WRITE |
POST | /api/v1/templates/:id/submit | Envia o template à Meta e marca PENDING | WPP_BIZ_TEMPLATES_WRITE |
POST | /api/v1/templates/:id/sync | Puxa o status atual da Meta para o cache | WPP_BIZ_TEMPLATES_WRITE |
GET | /api/v1/templates/analytics | Métricas de template na Meta (exige opt-in na WABA) | WPP_BIZ_ANALYTICS_READ |
GET | /api/v1/templates/quality | Nota de qualidade dos templates na Meta | WPP_BIZ_ANALYTICS_READ |
Campanhas — /wpp-business/api/v1/campaigns (8)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/campaigns | Cria campanha (exige template APPROVED) | WPP_BIZ_CAMPAIGNS_WRITE |
GET | /api/v1/campaigns | Lista campanhas | WPP_BIZ_CAMPAIGNS_READ |
GET | /api/v1/campaigns/:id | Busca campanha com contadores | WPP_BIZ_CAMPAIGNS_READ |
PATCH | /api/v1/campaigns/:id | Atualiza (só em DRAFT ou SCHEDULED) | WPP_BIZ_CAMPAIGNS_WRITE |
DELETE | /api/v1/campaigns/:id | Exclusão lógica (bloqueada em EXECUTING) | WPP_BIZ_CAMPAIGNS_WRITE |
POST | /api/v1/campaigns/:id/execute | Resolve destinatários e começa a enviar | WPP_BIZ_CAMPAIGNS_EXECUTE |
POST | /api/v1/campaigns/:id/pause | Pausa a campanha em execução | WPP_BIZ_CAMPAIGNS_EXECUTE |
GET | /api/v1/campaigns/:id/recipients | Lista destinatários e status individual | WPP_BIZ_CAMPAIGNS_READ |
Automações — /wpp-business/api/v1/automations (6)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/automations | Cria automação | WPP_BIZ_AUTOMATIONS_WRITE |
GET | /api/v1/automations | Lista automações | WPP_BIZ_AUTOMATIONS_READ |
GET | /api/v1/automations/:id | Busca automação | WPP_BIZ_AUTOMATIONS_READ |
PATCH | /api/v1/automations/:id | Atualiza automação | WPP_BIZ_AUTOMATIONS_WRITE |
DELETE | /api/v1/automations/:id | Exclusão lógica | WPP_BIZ_AUTOMATIONS_WRITE |
POST | /api/v1/automations/:id/toggle | Liga ou desliga | WPP_BIZ_AUTOMATIONS_WRITE |
Dispatchers — /wpp-business/api/v1/dispatchers (8)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/dispatchers | Cria dispatcher e a inscrição no Webhooks Engine | WPP_BIZ_DISPATCHERS_WRITE |
GET | /api/v1/dispatchers | Lista dispatchers | WPP_BIZ_DISPATCHERS_READ |
GET | /api/v1/dispatchers/:id | Busca dispatcher | WPP_BIZ_DISPATCHERS_READ |
PATCH | /api/v1/dispatchers/:id | Atualiza filtros, destino e retentativa | WPP_BIZ_DISPATCHERS_WRITE |
DELETE | /api/v1/dispatchers/:id | Remove dispatcher e a inscrição | WPP_BIZ_DISPATCHERS_WRITE |
POST | /api/v1/dispatchers/:id/toggle | Pausa ou retoma a entrega | WPP_BIZ_DISPATCHERS_WRITE |
GET | /api/v1/dispatchers/:id/delivery-logs | Log de entrega (page, pageSize, status) | WPP_BIZ_DISPATCHERS_READ |
POST | /api/v1/dispatchers/:id/delivery-logs/:logId/retry | Reenvia uma entrega que falhou | WPP_BIZ_DISPATCHERS_WRITE |
WhatsApp Flows — /wpp-business/api/v1/flows (9)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/flows | Cria o flow na Meta | WPP_BIZ_FLOWS_MANAGE |
GET | /api/v1/flows | Lista flows | WPP_BIZ_FLOWS_MANAGE |
GET | /api/v1/flows/:id | Busca flow | WPP_BIZ_FLOWS_MANAGE |
PATCH | /api/v1/flows/:id | Atualiza nome, categorias ou endpoint | WPP_BIZ_FLOWS_MANAGE |
PUT | /api/v1/flows/:id/json | Substitui a definição JSON na Meta | WPP_BIZ_FLOWS_MANAGE |
POST | /api/v1/flows/:id/publish | Publica o flow | WPP_BIZ_FLOWS_MANAGE |
POST | /api/v1/flows/:id/deprecate | Descontinua o flow | WPP_BIZ_FLOWS_MANAGE |
DELETE | /api/v1/flows/:id | Remove o flow | WPP_BIZ_FLOWS_MANAGE |
POST | /api/v1/flows/:id/send | Envia mensagem interativa com o flow | WPP_BIZ_MESSAGES_SEND |
Catálogo e produtos — /wpp-business/api/v1/catalogs (11)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/catalogs | Cria catálogo local | WPP_BIZ_CATALOG_MANAGE |
GET | /api/v1/catalogs | Lista catálogos | WPP_BIZ_CATALOG_READ |
GET | /api/v1/catalogs/:id | Busca catálogo | WPP_BIZ_CATALOG_READ |
DELETE | /api/v1/catalogs/:id | Exclusão lógica | WPP_BIZ_CATALOG_MANAGE |
POST | /api/v1/catalogs/:catalogId/products | Cria produto local | WPP_BIZ_CATALOG_MANAGE |
GET | /api/v1/catalogs/:catalogId/products | Lista produtos | WPP_BIZ_CATALOG_READ |
GET | /api/v1/catalogs/:catalogId/products/:pid | Busca produto | WPP_BIZ_CATALOG_READ |
PATCH | /api/v1/catalogs/:catalogId/products/:pid | Atualiza produto | WPP_BIZ_CATALOG_MANAGE |
DELETE | /api/v1/catalogs/:catalogId/products/:pid | Exclusão lógica | WPP_BIZ_CATALOG_MANAGE |
POST | /api/v1/catalogs/send-product | Envia mensagem de um produto | WPP_BIZ_CATALOG_MANAGE |
POST | /api/v1/catalogs/send-product-list | Envia lista de produtos por seção | WPP_BIZ_CATALOG_MANAGE |
Os dois
send-*exigem que o catálogo tenhametaCatalogId, e nenhum endpoint preenche esse campo hoje. Leia a §15 antes de planejar com base neles.
Pedidos — /wpp-business/api/v1/orders (3)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/orders | Lista pedidos | WPP_BIZ_ORDERS_READ |
GET | /api/v1/orders/:id | Busca pedido com itens | WPP_BIZ_ORDERS_READ |
PATCH | /api/v1/orders/:id/status | Muda o status do pedido | WPP_BIZ_ORDERS_MANAGE |
Pagamentos — /wpp-business/api/v1/payments (9)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/payments/config | Cria a configuração de pagamento da conta | WPP_BIZ_PAYMENTS_MANAGE |
GET | /api/v1/payments/config/:accountId | Lê a configuração | WPP_BIZ_PAYMENTS_READ |
PATCH | /api/v1/payments/config/:id | Atualiza a configuração | WPP_BIZ_PAYMENTS_MANAGE |
POST | /api/v1/payments | Cria o registro de cobrança de um pedido | WPP_BIZ_PAYMENTS_MANAGE |
GET | /api/v1/payments | Lista cobranças | WPP_BIZ_PAYMENTS_READ |
GET | /api/v1/payments/:id | Busca cobrança | WPP_BIZ_PAYMENTS_READ |
PATCH | /api/v1/payments/:id/status | Muda o status da cobrança | WPP_BIZ_PAYMENTS_MANAGE |
POST | /api/v1/payments/send-pix | Envia mensagem order_details com PIX | WPP_BIZ_PAYMENTS_MANAGE |
POST | /api/v1/payments/send-boleto | Envia mensagem order_details com boleto | WPP_BIZ_PAYMENTS_MANAGE |
Analytics — /wpp-business/api/v1/analytics (2)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/analytics/conversations | Analytics de conversa da Meta (exige accountId, start, end) | WPP_BIZ_ANALYTICS_READ |
GET | /api/v1/analytics/conversations/stats | Agregação local por categoria e origem | WPP_BIZ_ANALYTICS_READ |
Mídia — /wpp-business/api/v1/media (4)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/media/upload | Sobe arquivo para a Meta (multipart/form-data) | WPP_BIZ_MEDIA_MANAGE |
GET | /api/v1/media/:mediaId/url | Obtém a URL temporária do arquivo | WPP_BIZ_MEDIA_READ |
GET | /api/v1/media/:mediaId/download | Baixa o binário pela Meta | WPP_BIZ_MEDIA_READ |
DELETE | /api/v1/media/:mediaId | Remove o arquivo da Meta | WPP_BIZ_MEDIA_MANAGE |
Links curtos — /wpp-business/api/v1/short-links (5)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/short-links | Cria link wa.me com mensagem pronta | WPP_BIZ_SHORT_LINKS_MANAGE |
GET | /api/v1/short-links | Lista links | WPP_BIZ_SHORT_LINKS_MANAGE |
GET | /api/v1/short-links/:id | Busca link | WPP_BIZ_SHORT_LINKS_MANAGE |
DELETE | /api/v1/short-links/:id | Exclusão lógica | WPP_BIZ_SHORT_LINKS_MANAGE |
POST | /api/v1/short-links/:id/click | Incrementa o contador de cliques | WPP_BIZ_SHORT_LINKS_MANAGE |
Agentes e regras de distribuição — /wpp-business/api/v1/agents (11)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/agents | Cria agente a partir de um usuário do IAM | WPP_BIZ_AGENTS_MANAGE |
GET | /api/v1/agents | Lista agentes | WPP_BIZ_AGENTS_MANAGE |
GET | /api/v1/agents/available | Lista agentes disponíveis, por menor carga | WPP_BIZ_AGENTS_MANAGE |
GET | /api/v1/agents/:id | Busca agente | WPP_BIZ_AGENTS_MANAGE |
PATCH | /api/v1/agents/:id | Atualiza nome, e-mail, capacidade e habilidades | WPP_BIZ_AGENTS_MANAGE |
PATCH | /api/v1/agents/:id/status | Muda disponibilidade | WPP_BIZ_AGENTS_MANAGE |
DELETE | /api/v1/agents/:id | Exclusão lógica | WPP_BIZ_AGENTS_MANAGE |
POST | /api/v1/agents/routing-rules | Cria regra de distribuição | WPP_BIZ_AGENTS_MANAGE |
GET | /api/v1/agents/routing-rules | Lista regras | WPP_BIZ_AGENTS_MANAGE |
PATCH | /api/v1/agents/routing-rules/:id | Atualiza regra | WPP_BIZ_AGENTS_MANAGE |
DELETE | /api/v1/agents/routing-rules/:id | Exclusão lógica | WPP_BIZ_AGENTS_MANAGE |
Caixa de entrada — /wpp-business/api/v1/inbox (8)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /api/v1/inbox/assign | Cria ou atualiza a atribuição de uma conversa | WPP_BIZ_INBOX_MANAGE |
GET | /api/v1/inbox | Lista atribuições | WPP_BIZ_INBOX_MANAGE |
GET | /api/v1/inbox/:id | Busca atribuição | WPP_BIZ_INBOX_MANAGE |
POST | /api/v1/inbox/:id/transfer | Transfere para outro agente, com motivo | WPP_BIZ_INBOX_MANAGE |
POST | /api/v1/inbox/:id/resolve | Marca como resolvida | WPP_BIZ_INBOX_MANAGE |
POST | /api/v1/inbox/:id/close | Fecha a conversa | WPP_BIZ_INBOX_MANAGE |
POST | /api/v1/inbox/:id/notes | Adiciona nota interna (só agente registrado) | WPP_BIZ_INBOX_MANAGE |
GET | /api/v1/inbox/:id/notes | Lista notas | WPP_BIZ_INBOX_MANAGE |
Webhook da Meta e log — /wpp-business/api/v1/webhook (2 + 4)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /api/v1/webhook | Verificação hub.challenge da Meta | Pública — casa o webhookVerifyToken da conta |
POST | /api/v1/webhook | Recebe eventos da Meta | Pública — autentica por HMAC x-hub-signature-256 |
GET | /api/v1/webhook/logs | Lista logs de webhook | WPP_BIZ_WEBHOOKS_READ |
GET | /api/v1/webhook/logs/stats | Estatísticas de processamento | WPP_BIZ_WEBHOOKS_READ |
GET | /api/v1/webhook/logs/:id | Busca um log com o payload cru | WPP_BIZ_WEBHOOKS_READ |
POST | /api/v1/webhook/logs/:id/replay | Reprocessa o payload guardado | WPP_BIZ_WEBHOOKS_WRITE |
Saúde
| Método | Rota | Descrição |
|---|---|---|
GET | /wpp-business/health | Identificaçã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"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name | string (1–200) | Sim | Nome de exibição interno |
appId | string | Sim | App ID do Meta for Developers. Único por organização. |
appSecret | string | Sim | App 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
| Status | Quando |
|---|---|
400 | Corpo reprovado no Zod |
403 | Token sem organizationId ou sem WPP_BIZ_ACCOUNTS_MANAGE |
409 | appId 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"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
accessToken | string | Sim | Token de system user da Meta com os escopos de messaging e management |
phoneNumberId | string | Sim | ID do número na Meta |
wabaId | string | Não, mas mande sempre | A 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 |
name | string (≤200) | Não | Nome da conta. Sem ele, usa o nome verificado do número. |
description | string (≤2000) | Não | Descriçã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
| Status | details.code | Quando |
|---|---|---|
400 | APP_NOT_REGISTERED | O token foi emitido por um Meta App que a organização não registrou em POST /apps. O appId vem no corpo da resposta. |
400 | META_WABA_ID_REQUIRED | Não foi possível descobrir a WABA — mande wabaId no corpo |
401 | META_TOKEN_INVALID / META_TOKEN_EXPIRED | Token da Meta inválido, expirado ou revogado |
403 | META_INSUFFICIENT_SCOPE | Falta 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"] }
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
fields | string[] | 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. |
overrideCallbackUrinão é mais aceito — o schema rejeita explicitamente. A URL de callback é derivada deWPP_BUSINESS_WEBHOOK_BASE_URLe é 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
| Status | Quando |
|---|---|
400 | Conta sem app vinculado, sem webhookVerifyToken, ou WPP_BUSINESS_WEBHOOK_BASE_URL ausente |
401 | A Meta recusou o token da conta ao inscrever a WABA |
404 | Conta 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" }
]
}
]
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
phoneNumberId | string (UUID) | Sim | UUID interno do número (WppBizPhoneNumber.id), não o ID da Meta |
to | string | Sim | Telefone do destinatário no formato internacional, só dígitos (5511999998888) |
templateName | string | Sim | Nome exato do template aprovado na Meta |
languageCode | string | Sim | Código do idioma do template (pt_BR, en_US) |
components | object[] | Não | Componentes 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
| Status | details.code | Quando |
|---|---|---|
400 | — | Corpo reprovado no Zod, ou template rejeitado pela Meta (parâmetro faltando, nome errado) |
401 | META_TOKEN_EXPIRED / META_TOKEN_REVOKED | Token da conta inválido — atualize em PATCH /accounts/:id |
403 | META_INSUFFICIENT_SCOPE | O token da Meta não tem whatsapp_business_messaging |
404 | — | phoneNumberId não existe nesta organização |
429 | META_RATE_LIMIT | Limite 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." }
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
phoneNumberId | string (UUID) | Sim | UUID interno do número |
to | string | Sim | Destinatário |
type | TEXT | IMAGE | VIDEO | AUDIO | DOCUMENT | LOCATION | INTERACTIVE | Sim | Tipo da mensagem |
content | object | Sim | O 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âmetro | Obrigatório | Descrição |
|---|---|---|
phoneNumberId | Sim | UUID 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
| Status | Corpo | Quando |
|---|---|---|
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 inicial — GET /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.
filtersvazio 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 ototalRecipientslogo depois doexecute, antes de tomar café.- A campanha só sai com o template
APPROVED. O BB recusa com400se o status local não estiver aprovado — e o status local só muda comPOST /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
UTILITYe a Meta discorda, ela aprova comoMARKETINGem 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 emGET /accounts/:id/templatesdepois 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
UTILITYpor 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. pausedemora até um lote. O envio roda em lotes de 50 e opausesó é 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
PENDINGpara sempre (§15). deliveredCountereadCountnão sobem. Os envios de campanha não criam registro emwpp_biz_messages, então o status update da Meta não encontra o destinatário para atualizar. UsesentCountefailedCount, 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.
IGNOREDquase sempre é número desconhecido. O log ficaIGNOREDquando ophone_number_iddo payload não bate com nenhumWppBizPhoneNumber. Confira se o número foi vinculado à conta.- O replay não valida assinatura, por desenho. Só quem tem
WPP_BIZ_WEBHOOKS_WRITEdeve receber essa permissão. - O replay de eventos de mensagem hoje não reprocessa. O log guarda o
valuedo 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.
messageTypeseeventsse combinam, não se substituem. Passar os dois gera a união dos filtros. Se nenhum for informado em modoBASIC, 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_tokene recusa se o app do token não bater com oWppBizAppvinculado. - Token de system user permanente ainda pode ser revogado. Monitore
META_TOKEN_REVOKEDnos 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/webhooksdepois 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 eventowpp-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.skillseconditionsdas regras não influenciam a distribuição. A escolha automática é sempre o agente disponível de menor carga (§15).POST /inbox/:id/noteshoje não funciona com token JWT. A rota resolve o agente por um campo que o token não carrega e responde403. Registre a nota no seu sistema até a correção (§15).
12Integração com outros building blocks
| Building block | Como se relaciona | Obrigatório |
|---|---|---|
| IAM | Emite o token, define o organizationId que isola cada WABA e o vocabulário WPP_BIZ_* de permissões | Sim |
| Webhooks Engine | Cada dispatcher é uma inscrição lá; entrega, retentativa e log de entrega são dele | Sim, para dispatchers |
| API Keys | Alternativa ao JWT: X-API-Key ou Authorization: ApiKey <chave>, com permissão por padrão de recurso | Não |
Payments (src/payments) | Processa o PIX ou o boleto de verdade; o WPP Business só entrega a mensagem de cobrança | Não |
| Commerce | Dono do catálogo, do pedido e do estoque de verdade; o WhatsApp é a vitrine e o canal | Não |
| Customers | Cadastro oficial do cliente; o WppBizContact é o identificador de conversa, não o cadastro | Não |
| Audit Trail | Registra quem enviou o quê para quem, quando a operação é regulada | Nã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 hoje | Nã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ável | Descrição | Obrigatória | Padrão |
|---|---|---|---|
WPP_BUSINESS_CREDENTIAL_MASTER_KEY | Chave 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_URL | URL pública do serviço. A callback registrada na Meta é <base>/wpp-business/api/v1/webhook. | Para configurar webhook | — |
MODULE_WPP_BUSINESS_PORT | Porta no modo standalone | Não | 3025 |
MODULE_WPP_BUSINESS_URL | URL do módulo para chamadas entre building blocks | Não | '' |
DATABASE_URL | PostgreSQL. O BB usa o schema wpp_business. | Sim | — |
JWT_SECRET | Verificação do token do IAM | Sim | — |
DEPLOYMENT_MODE | monolith ou standalone | Não | monolith |
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ência | Para quê |
|---|---|
| PostgreSQL | Schema wpp_business, 27 tabelas |
| Meta Graph API v25.0 | Todo envio, template, mídia, flow e consulta de estado |
| Webhooks Engine | Entrega dos eventos configurados por dispatcher |
| IAM | Verificação de token e vocabulário de permissões |
Limites e quotas
| Limite | Valor | Origem |
|---|---|---|
| Importação em massa de contatos | 1.000 por chamada | Schema Zod do BB |
| Lote de envio de campanha | 50 mensagens, com 1 segundo entre lotes | Implementação do BB |
| Retentativas no cliente da Meta | 3 tentativas, backoff 1s → 2s | Implementação do BB |
| Timeout da chamada à Meta | Não há — ver §15 | — |
| Corpo de mensagem interativa | 1.024 caracteres | Cloud API |
| Rodapé de mensagem interativa | 60 caracteres | Cloud API |
| Seções em lista de produtos | 10 | Cloud API |
| Tipos de mídia aceitos no upload | JPEG, PNG, MP4, AAC/MP4/MPEG/OGG, PDF, Office | Schema 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.
| Limite | Valor | Fonte |
|---|---|---|
| Tiers de envio (usuários únicos por 24h) | 250 → 2.000 → 10.000 → 100.000 → ilimitado | Messaging Limits |
| Como sair de 250 | Verificação de negócio, verificação via parceiro, ou 2.000 mensagens entregues fora da janela em 30 dias com template de alta qualidade | idem |
| Escalonamento automático | Em até 6 horas, se a qualidade estiver alta e você tiver usado ao menos metade do limite atual nos últimos 7 dias | idem |
| Throughput por número | 80 mensagens por segundo, com upgrade disponível | Cloud API Overview |
| Mesmo destinatário | 1 mensagem a cada 6 segundos (rajada de até 45 em 6s, com espera proporcional) | idem |
| Requisições de API | 200 por hora por app e WABA; 5.000 por hora para WABAs ativas | idem |
| Janela de entrada gratuita (anúncio Click-to-WhatsApp) | 72 horas, com qualquer tipo de mensagem sem custo | Pricing |
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
| Status | code / details.code | Significa | O que fazer |
|---|---|---|---|
400 | VALIDATION | Corpo reprovado no Zod | Confira campos e tipos contra a §9 |
400 | BAD_REQUEST | Pré-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 |
400 | APP_NOT_REGISTERED | O token da Meta pertence a um app não registrado; o appId vem na resposta | POST /apps com esse appId |
400 | META_WABA_ID_REQUIRED | Não deu para descobrir a WABA a partir do número | Mande wabaId no corpo |
400 | META_API_ERROR | A Meta recusou a chamada (template inválido, janela fechada, parâmetro faltando) | Leia details.metaMessage e details.metaTraceId |
401 | META_TOKEN_EXPIRED | Token da Meta expirou | PATCH /accounts/:id com token novo |
401 | META_TOKEN_REVOKED | Token revogado pela Meta ou pelo administrador | Gere outro no Business Manager |
401 | META_TOKEN_INVALID | Token inválido | Confira se copiou o token certo |
401 | UNAUTHORIZED | Token do IAM ausente, inválido ou expirado; ou assinatura de webhook inválida | Renove o token; no webhook, confira o App Secret |
403 | FORBIDDEN | Falta permissão WPP_BIZ_* ou organizationId no token | Confira o token e as permissões contratadas |
403 | META_INSUFFICIENT_SCOPE | O token da Meta não tem o escopo exigido | Regere com whatsapp_business_messaging e whatsapp_business_management |
404 | NOT_FOUND | Recurso inexistente, excluído logicamente ou de outra organização | Confira o ID e o tenant |
409 | CONFLICT | appId, wabaId, telefone, etiqueta, retailerId ou agente já existentes | Escolha outro identificador |
429 | META_RATE_LIMIT | Limite de taxa da Meta | Recue com backoff próprio — o BB não retenta 429 |
500 | INTERNAL | Falha no banco ou erro não classificado da Meta | Consulte o log; se for da Meta, details.metaTraceId abre chamado |
Observabilidade
GET /wpp-business/healthdevolve 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/statsresume.- Todo erro da Meta carrega
fbtrace_idemdetails.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.receivedewpp-biz.message.status.updated, há eventos granulares por tipo e por status, além dewpp-biz.campaign.started/completed,wpp-biz.template.status.changed,wpp-biz.account.alert,wpp-biz.phone.quality.updatedewpp-biz.conversation.*. wpp-biz.phone.quality.updatedewpp-biz.account.alertsã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:
WppBizContacttem exclusão lógica (deletedAt);WppBizMessagenão tem — a mensagem sobrevive à exclusão do contato, comcontactIdvirando nulo.WppBizWebhookLogguarda 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ção | Impacto | Situação |
|---|---|---|
| Catálogo não sincroniza com o Commerce Manager | WppBizCatalog.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 API | WppBizOrder 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á integrado | send-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 Meta | O 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 existe | O 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 QR | Com 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ção | Impacto | Situação |
|---|---|---|
| O webhook não persiste o estado vindo da Meta | Aprovaçã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 fila | Sem 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 agendador | O 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 sobem | Os 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 tagIds | Qualquer 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órico | Só POST /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 mensagens | O 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 JWT | A 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 cobertos | order, 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 expurgo | Nã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ção | Impacto | Situação |
|---|---|---|
| Mensagem recebida não abre nem roteia atendimento | Nada 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 skills | Qualquer 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ízio | Escolhe 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ção | A regra é aceita e cai no comportamento padrão, em silêncio. | Não implementado |
| SLA é gravado e nunca verificado | slaDeadlineAt é 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 inflado | Reatribuir 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ção | Impacto | Situação |
|---|---|---|
| Não há timeout nas chamadas à Graph API | Uma chamada pendurada na Meta segura a requisição do cliente indefinidamente. | Roadmap |
Erro 400 permanente é retentado três vezes | Falhas 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 backoff | Limite 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 retry | Essas 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ódigo | Subir de v25.0 exige deploy, não configuração. | Por desenho |
Fora do escopo, por desenho
| Não faz | Onde está |
|---|---|
| Bot com IA em linguagem natural — a automação aqui casa palavra-chave e executa só a primeira regra que casar | AI Engine (src/ai-engine) consumindo o evento e respondendo por POST /messages/send |
| Construtor visual de fluxo conversacional | WhatsApp Flows da Meta, editado por JSON |
Conexão não oficial via WhatsApp Web (Baileys, whatsapp-web.js) | Building block wpp |
| SMS, voz, push | Fora do catálogo hoje; e-mail está em building block próprio |
| Gestão de consentimento e opt-in | Sua 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.mdestá desatualizado: ele descreve o building block wpp (multi-driver com Baileys, fila BullMQ, prefixo/wpp/api/v1, respostas202comcorrelationId), e nada daquilo se aplica aqui. Em qualquer divergência entredocs/e este arquivo, vale este arquivo.
Padrão: PADRAO-DOCUMENTACAO.md · Índice: INDEX.md · Credenciais de ambiente: AMBIENTES.md