Catalisa.
Building blocks/ComunicaçãoBeta

Meta Account

Provisiona e monitora sua conta Meta Business com a WABA no seu próprio nome

36
Endpoints
11
Entidades
1
Provedores
Tenant
Escopo
3028
Porta

A conta oficial de WhatsApp é sua, no seu Business Manager. A Catalisa opera em cima dela com um token que você gera e pode revogar — e se um dia você trocar de fornecedor, o número e a conta continuam sendo seus.

Para quem é
  • Empresas que já atendem no WhatsApp e descobriram que a conta oficial está no nome do fornecedor
  • Fintechs e varejistas com vários números e várias WABAs, que perderam o controle de quem acessa o quê
  • Times de produto que integram a Cloud API da Meta direto e travam no onboarding e na expiração de token
Substitui
  • Depender do painel do BSP para saber o estado da sua própria conta Meta
  • Planilha de controle de números, WABAs, apps, system users e tokens
  • Abrir chamado no fornecedor para configurar webhook ou verificar número
O que não é
  • Um BSP — a Catalisa não revende conversa nem estende linha de crédito da Meta
  • A camada de envio de mensagem, campanha ou atendimento (isso é o wpp-business)
  • Uma implementação de Embedded Signup — hoje o token é gerado por você no seu Business Manager

01Resumo executivo

O Meta Account é a camada que cuida da sua relação com a Meta antes de qualquer mensagem sair. Ele descobre, espelha e vigia tudo que existe na sua conta Meta Business: as contas de WhatsApp (WABA), os números, os templates, os aplicativos, os usuários de sistema, os catálogos e os webhooks. Você entrega um identificador de negócio e um token; ele devolve o mapa completo e passa a avisar quando algo muda.

Na prática, ele resolve o dia em que o time de atendimento diz que "o WhatsApp parou" e ninguém sabe se o problema é o token que expirou, o webhook que foi apontado para o lugar errado, o número que caiu de qualidade ou o template que a Meta reprovou. Em vez de abrir quatro telas diferentes no painel da Meta e um chamado no fornecedor, você chama GET /businesses/:id/token/health e GET /businesses/:id/sync/drift e tem a resposta.

Está em beta desde abril de 2026. O código roda no monolito e tem entry point standalone na porta 3028, mas ainda não está publicado nos ambientes de staging e produção — não aparece em infra/TOPOLOGY.md nem em AMBIENTES.md. A cobertura de testes automatizados é um único teste ponta a ponta contra a API real da Meta. Trate como pré-produção e leia a §15 antes de prometer prazo a cliente.

AtributoValor
Identificadormeta-account
CategoriaComunicação
EscopoTenant (exige organizationId no token, em todas as rotas)
Porta (standalone)3028
Path alias@meta-account
Prefixo HTTP/meta-account
StatusBeta desde 2026-04
Depende dePostgreSQL (schema meta_account), Redis, Meta Graph API v25.0
PermissõesMETA_ACCOUNT_READ, META_ACCOUNT_MANAGE

02O problemanegócio

O cenário. Uma empresa decide atender e vender pelo WhatsApp. Contrata um fornecedor, recebe um número funcionando em duas semanas e fica satisfeita. Dois anos depois tem seis números, duas WABAs, quarenta templates e três aplicativos — e não tem ideia de onde nada disso mora, nem de quem é.

O que trava hoje.

  • A conta oficial pode não ser sua. No modelo Solution Partner, a Meta fatura o parceiro, e não você: os clientes trazidos pelo parceiro usam a linha de crédito dele (Solution Partner overview). Você não tem relação financeira direta com a Meta e não vê o custo de origem.
  • Sair depende de quem você quer deixar. A migração de WABA entre parceiros é iniciada pelo provedor atual, que precisa marcar a conta e desabilitar a verificação em duas etapas do número (documentação da Meta). O cliente não migra sozinho. Quem está perdendo a conta tem, na prática, um veto operacional.
  • Os recursos estão espalhados por telas que não conversam. Business Manager, configurações da WABA, configurações do número, painel do aplicativo. Responder "qual app está inscrito em qual WABA" exige navegar por três lugares e confiar na memória.
  • Os webhooks existem em três níveis. Aplicativo, WABA e número. Cada um é configurado em um lugar diferente, com uma regra de herança diferente, e a falha silenciosa é o modo de falha padrão: o evento simplesmente não chega.
  • Os erros da Meta não dizem nada. O código 190 com subcódigo 463 significa "seu token de desktop expirou porque tokens desse tipo duram cerca de 60 dias". Ninguém descobre isso lendo a mensagem original.
  • O token expira e ninguém percebe. Até o cliente reclamar.

O custo de não resolver. O custo direto é a janela de indisponibilidade que ninguém detecta — se o webhook cai, as mensagens dos seus clientes chegam ao WhatsApp e não chegam ao seu sistema, e o único alarme é a reclamação. O custo estrutural é maior e mais lento: quanto mais tempo a conta oficial fica no nome de um terceiro, mais caro fica sair, porque a saída depende da cooperação de quem está sendo trocado. E há um prazo concreto no horizonte para quem opera no Brasil: a Meta determinou que clientes elegíveis migrem todas as WABAs para faturamento em BRL até 30 de junho de 2027, sob risco de interrupção de serviço (Pricing on the WhatsApp Business Platform, consulta em 2026-08-16). Quem não sabe onde estão as próprias WABAs não consegue cumprir esse prazo.


03Proposta de valornegócio

AntesDepois
A conta oficial está no Business Manager do fornecedorA WABA está no seu Business Manager; a Catalisa só usa um token que você gera
Descobrir o que existe na conta é navegação manual em quatro telasUm POST /discover mapeia o portfólio inteiro e grava o espelho
Webhook em três níveis, configurado a mão, falhando em silêncioPOST /webhooks/auto-configure resolve os níveis de aplicativo e WABA de uma vez
"O WhatsApp parou" vira investigação de meia manhãGET /token/health e GET /sync/drift respondem em duas chamadas
Erro 190 subcódigo 463 no log"Token expirado. Tokens de aplicativo desktop expiram em cerca de 60 dias. Use um token de system user."

A conta é do cliente, por construção. O BB não tem Business Manager próprio. A entrada obrigatória de POST /discover é o identificador do seu negócio na Meta e um token gerado por você. Não existe caminho no código em que a Catalisa seja dona da WABA — a §5 e a §14 mostram onde isso está no código.

Descoberta em vez de cadastro. Você não digita a lista de números, WABAs e templates. O DiscoveryService percorre o grafo da Meta e grava tudo, e as chamadas seguintes são idempotentes: rodar de novo atualiza, não duplica.

A divergência vira dado. O banco é um espelho consultável, e a Meta continua sendo a fonte de verdade. GET /sync/drift compara os dois e devolve campo a campo o que mudou sem você saber.

Erro traduzido com o que fazer. O meta-error-translator mapeia mais de vinte códigos da Meta para erros da plataforma com orientação em texto claro, preservando o objeto original em details.meta para depuração.

Segredo cifrado por organização. O token da Meta e o segredo do aplicativo ficam cifrados em AES-256-GCM no banco, por tenant. Nunca em variável de ambiente compartilhada, nunca de volta na resposta.


04Casos de uso reaisnegócio

Caso 1 — Um varejista descobre, no meio da migração, que a conta oficial não é dele Cenário ilustrativo

Contexto. Rede de varejo com quatro números de WhatsApp, atendimento e campanhas rodando há dois anos por um fornecedor único.

A dor. Ao negociar a troca de plataforma, o time descobre que a WABA foi criada pelo fornecedor, no Business Manager do fornecedor. A saída passa a depender de quem está sendo trocado — a Meta exige que o provedor atual inicie a migração e desabilite a verificação em duas etapas dos números. A negociação de contrato vira negociação de refém, e o prazo do projeto dobra.

A solução com o BB. No modelo do Meta Account, a WABA nasce no Business Manager do próprio varejista. O onboarding começa com o cliente gerando um token de system user dentro da conta dele e chamando POST /meta-account/api/v1/discover com o identificador do negócio e esse token. A Catalisa passa a operar com uma credencial delegada, revogável em um clique no painel da Meta.

O resultado. Trocar de fornecedor deixa de exigir migração de WABA: revoga-se o token e emite-se outro. O número, o histórico de qualidade e os templates ficam onde sempre estiveram — na conta do varejista.

Caso 2 — Uma fintech para de perder mensagem por webhook mal configurado Cenário ilustrativo

Contexto. Fintech de crédito com seis números em duas WABAs, três aplicativos Meta e times diferentes cuidando de originação e cobrança.

A dor. Depois de um ajuste em um dos aplicativos, as mensagens de uma das WABAs pararam de chegar ao sistema. Levou dois dias para alguém perceber, porque o envio continuava funcionando — só o recebimento tinha parado. A configuração estava correta no nível do aplicativo e errada no nível da WABA, e não havia nenhuma tela que mostrasse os dois ao mesmo tempo.

A solução com o BB. GET /meta-account/api/v1/businesses/:businessId/webhooks/status consulta a Meta ao vivo e devolve, num único documento, o que está inscrito no nível de aplicativo e no nível de WABA. O POST .../webhooks/auto-configure reaplica a configuração em todos os aplicativos com segredo cadastrado e em todas as WABAs do negócio, devolvendo sucesso ou falha por recurso.

O resultado. A verificação vira uma chamada, e a correção vira outra. Vale registrar o limite honesto: o relatório de status não inspeciona nível de número, porque a Meta não expõe assinatura por número — os eventos trafegam pela WABA e são identificados por metadata.phone_number_id na aplicação.

Caso 3 — Sair de um BSP depende do BSP que você quer deixar Referência de mercado

Contexto. A própria Meta documenta o procedimento de migração de WABA entre soluções parceiras (Migrating a WABA from one Multi-Partner Solution to another, consulta em 2026-08-16).

A dor do mercado. O processo é iniciado pelo provedor atual, que marca a WABA para migração e envia os detalhes ao parceiro de destino; ao cliente cabe aceitar. A migração de número entre parceiros exige ainda que a verificação em duas etapas do número esteja desabilitada e que o nome de exibição esteja aprovado (migração de número entre Solution Partners). Mesmo quando dá certo, os templates duplicados começam com avaliação UNKNOWN nas primeiras 24 horas. Consultando as documentações públicas de Twilio, Bird e Infobip em 2026-08-16, encontramos guias detalhados de migração para dentro da plataforma e nenhum de migração para fora.

Como a Catalisa endereça. A Meta suporta explicitamente o modelo em que o cliente cria a própria conta e a compartilha com o parceiro — "use Meta Business Suite to create a WhatsApp Business account on their own and share it with you" (Solution Partner overview). É esse o modelo que o Meta Account implementa: o BB não cria Business Manager, não é dono de WABA e não tem linha de crédito. Ele consome um token delegado.

O resultado. A propriedade do ativo não é promessa contratual, é consequência de arquitetura. O caminho de saída não passa por pedir autorização a ninguém.

Caso 4 — O token de 60 dias que expira num sábado Cenário ilustrativo

Contexto. Operação de cobrança que dispara lembretes por template todos os dias, inclusive fim de semana.

A dor. O token usado na integração tinha sido gerado no painel do desenvolvedor, não como token de system user, e expirou sessenta dias depois — num sábado. Os disparos falharam com erro 190, subcódigo 463, e a mensagem original da Meta não explicava nada. A operação ficou parada até segunda.

A solução com o BB. O TokenHealthService chama a introspecção da Meta, classifica o token em VALID, EXPIRING_SOON, EXPIRED, INVALID ou MISSING_SCOPES e publica o evento meta-account.token.healthChanged nos três últimos estados e no de expiração próxima. A janela de alerta de EXPIRING_SOON é de sete dias. O GET .../token/health devolve expiresInDays e um texto de orientação — no caso de token temporário, a recomendação de trocar por token de system user, que pode ser configurado para não expirar.

O resultado. O problema é detectado com uma semana de antecedência e em horário comercial, em vez de descoberto pelo cliente no sábado.


05Mercado e diferenciaisnegócio

Panorama. O acesso à API do WhatsApp passa por um ecossistema de parceiros da Meta, dividido em duas figuras com consequências bem diferentes. O Solution Partner (o que o mercado chama de BSP) tem linha de crédito e pode estendê-la aos clientes que traz; nesse arranjo a Meta fatura o parceiro, e o parceiro fatura o cliente. O Tech Provider não tem linha de crédito: "clients onboarded by Tech Providers must provide their own payment method", e "Meta will then bill these clients for API usage" (Solution Partner overview, consulta em 2026-08-16).

Essa distinção é a raiz econômica do aprisionamento. No modelo BSP, o cliente não tem relação financeira direta com a Meta, não vê o preço de custo, e — quando decide sair — descobre que o procedimento de migração precisa ser iniciado pelo parceiro que ele está deixando. Isso não é conjectura de mercado: está na documentação da própria Meta, citada na §4.

O Meta Account não compete com BSP em conectividade. Ele resolve a camada que quase nenhum deles entrega: a gestão do portfólio Meta como recurso de primeira classe, com o ativo no nome do cliente.

CritérioCatalisa Meta Account360dialogTwilioInfobipTake BlipCloud API direta
Quem é dono da WABACliente, sempreCliente, no BM deleClienteCliente finalNão declarado publicamenteCliente
Quem fatura o uso da MetaMeta, ao clienteMeta, ao cliente (taxas separadas)Twilio, com taxa por mensagemInfobip, via linha de crédito própriaTake Blip, plano com franquiaMeta, ao cliente
Doc pública de migração de saídaNão se aplica (token revogável)Posicionamento anti-lock-in publicadoNão localizadaNão localizadaNão localizadaNão se aplica
Descoberta automática do portfólioSim, um POSTNãoNãoNãoNãoVocê implementa
Webhooks nos três níveis unificadosSim (aplicativo e WABA)NãoParcialNãoNãoVocê implementa
Detecção de divergênciaSimNãoNãoNãoNãoVocê implementa
Saúde de token com alertaSim, com eventoNãoNãoNãoNãoVocê implementa
Envio de mensagem e campanhaNão (é o wpp-business)SimSimSimSimVocê implementa
Linha de crédito da MetaNãoNãoNãoSimSimNão

Comparativo montado a partir das documentações públicas dos fornecedores em 2026-08-16. As lacunas marcadas como "não localizada" significam exatamente isso — não encontramos documentação pública, o que não equivale a afirmar que o recurso não exista. Para Take Blip, Zenvia e Gupshup não localizamos declaração pública sobre a propriedade da WABA.

Nossos diferenciais

  1. A propriedade do ativo é consequência do código, não do contrato. A entrada de POST /discover é o identificador do negócio do cliente mais um token do cliente. Não existe Business Manager da Catalisa no caminho, não há linha de crédito estendida e não há chamada que crie WABA em nome de terceiro. Um concorrente que já tenha milhares de WABAs no próprio Business Manager não copia isso com uma mudança de produto — teria que migrar a base inteira, com a cooperação de cada cliente.
  2. A descoberta substitui o cadastro. Um POST percorre o grafo da Meta — WABAs próprias e de cliente, números, templates, aplicativos, system users, catálogos — e grava o espelho. É difícil de copiar não pela chamada em si, mas pelo tratamento: 31 métodos de cliente HTTP, tradução de mais de vinte códigos de erro e reconciliação idempotente com exclusão lógica de quem sumiu.
  3. Os três níveis de webhook em um lugar só. A Meta separa assinatura de aplicativo, assinatura de WABA e roteamento por número em contextos distintos, com tokens de autenticação distintos — o nível de aplicativo usa {appId}|{appSecret} como credencial, o de WABA usa o token do negócio. O BB encapsula as duas formas e expõe status e configuração unificados.
  4. O espelho é consultável e a divergência é explícita. Ter os recursos da Meta em Postgres permite consulta, junção e histórico de sincronização — e permite responder "o que mudou lá sem eu saber" sem depender do suporte de ninguém.

Quando escolher o concorrente. Se o que você precisa é começar a mandar mensagem rápido, sem construir nada, escolha um BSP: 360dialog, Twilio, Infobip, Take Blip e Zenvia entregam onboarding, conectividade e envio hoje, e o Meta Account sozinho não envia mensagem nenhuma — ele provisiona. Se você precisa de linha de crédito da Meta, porque não quer ou não pode colocar método de pagamento próprio, o modelo Solution Partner resolve isso e a Catalisa não. Se seu volume é de um número só e uma WABA só, a complexidade que este BB administra não existe no seu caso, e o painel da Meta basta. E se seu time já opera a Cloud API direta com maturidade, tem monitoramento próprio e não sente dor de descoberta nem de expiração de token, o ganho aqui é marginal. O Meta Account ganha quando o problema é muitos ativos Meta, muitas mãos e a exigência de que a conta oficial seja do cliente — não quando o problema é entregar o primeiro número em uma semana.


06Modelo de cobrança e ROInegócio

Precificação em definição. O Meta Account não tem preço fechado. Ele é uma camada de provisionamento e governança, não um canal de mensagem, e a intenção é que acompanhe a contratação do wpp-business em vez de ser vendido isolado. Não há valor a divulgar, e este documento não estima nenhum.

O que dispara custo.

DriverPor quê
Business Managers conectadosCada um é um espelho e um token a vigiar
WABAs e números monitoradosVolume de sincronização e de verificação de divergência cresce com eles
Frequência de sincronizaçãoCada fullSync percorre o grafo inteiro na Graph API

O custo que não é nosso. O custo de mensagem é da Meta e vai direto para o cliente, porque a conta é do cliente. Desde 1º de julho de 2025 a Meta cobra por mensagem entregue, e não mais por conversa (Pricing on the WhatsApp Business Platform; a página de cobrança por conversa está marcada como substituída). O que isso significa por categoria:

Categoria de templateCobrança
MarketingSempre cobrada
UtilityCobrada fora da janela de atendimento; gratuita dentro de janela aberta
AuthenticationCobrada fora da janela; sujeita a faixas de volume com taxas menores
ServiceGratuita para todos os negócios desde 1º de novembro de 2024

Também são gratuitas as mensagens dentro da janela de ponto de entrada gratuito de 72 horas, aberta quando o usuário chega por anúncio Click to WhatsApp ou por botão de página do Facebook e o negócio responde em 24 horas.

Comparação de custo — cenário nomeado: operação com 3 números, 1 WABA e 200 mil mensagens de template por mês.

Catalisa Meta Account360dialogTwilioTake Blip
Base de cálculoPrecificação em definição€49 a €249 por número/mês + taxas Meta à parteUS$ 0,005 por mensagem sobre o custo MetaPlano mensal com franquia de conversas
Camada de mensagem inclusaNão — é o wpp-businessSimSimSim
Custo MetaFaturado direto ao clienteCobrado à parte, explicitamenteEmbutido na fatura TwilioEmbutido no plano
Transparência do custo de origemTotal (conta é do cliente)AltaMédiaBaixa

Preços públicos consultados em 2026-08-16: 360dialog (página datada de 14/08/2026), Twilio WhatsApp, Take Blip (sem data visível). Infobip e Gupshup não publicam preço em página aberta. Não reproduzimos tarifa da Meta por país porque a documentação oficial distribui os valores em arquivos anexos, não na página — baixe o rate card vigente antes de qualquer proposta. Todos os números acima são dos fornecedores, não estimativas nossas.

ROI. A conta de guardanapo tem duas linhas e nenhuma delas é licença.

A primeira é a indisponibilidade que ninguém detecta. Um webhook desconfigurado não gera erro visível: o envio segue funcionando e só o recebimento para. Se a sua operação depende de resposta do cliente — cobrança, confirmação, atendimento — cada hora nesse estado é conversa perdida. Detecção por reclamação custa dias; GET /webhooks/status custa uma chamada.

A segunda é o custo de saída. Enquanto a conta oficial estiver no nome de um fornecedor, trocar de fornecedor exige a cooperação dele, e o poder de negociação na renovação de contrato é dele. Com a conta no seu nome, o custo de saída é revogar um token. Esse é o item de maior valor econômico e o mais difícil de colocar em planilha, porque ele só aparece no dia em que você precisa dele.


07Arquitetura

                            HTTP  (Bearer JWT emitido pelo IAM)
                              │
  ┌───────────────────────────┴──────────────────────────────────────────┐
  │ Hono app  basePath('/meta-account')  ·  applyCommonMiddleware        │
  │                                                                      │
  │  /api/v1/discover                     discoveryRouter                │
  │  /api/v1/businesses                   businessRouter  (+ /:id/graph) │
  │  /api/v1/wabas                        wabaRouter                     │
  │  /api/v1/phone-numbers                phoneNumberRouter              │
  │  /api/v1/templates                    templateRouter                 │
  │  /api/v1/apps                         appRouter                      │
  │  /api/v1/system-users                 systemUserRouter               │
  │  /api/v1/webhooks                     webhookRouter                  │
  │  /api/v1/businesses/:id/webhooks      webhookBusinessRouter          │
  │  /api/v1/businesses/:id/system-users  systemUserBusinessRouter       │
  │  /api/v1/businesses/:id/catalogs      catalogRouter → productRouter  │
  │  /api/v1/businesses/:id/sync          syncRouter                     │
  │  /api/v1/businesses/:id/token         tokenRouter                    │
  └───────────────────────────┬──────────────────────────────────────────┘
              Zod parse → ResultAsync<T, AppError> → handleResult
  ┌───────────────────────────┴──────────────────────────────────────────┐
  │ services/ (12)                                                       │
  │   DiscoveryService    orquestra o onboarding inteiro                 │
  │   SyncService         fullSync · syncResource · detectDrift          │
  │   TokenHealthService  introspecção → classificação → evento          │
  │   WebhookService      níveis de app e WABA + auto-configure          │
  │   Business · Waba · PhoneNumber · Template · App                     │
  │   SystemUser · Catalog · Product                                     │
  └──────────────┬──────────────────────────────┬────────────────────────┘
                 │                              │
  ┌──────────────┴─────────────┐   ┌────────────┴─────────────────────────┐
  │ repositories/ (11, Prisma) │   │ providers/meta-graph                 │
  │ PostgreSQL                 │   │   MetaGraphClient — 31 métodos       │
  │ schema "meta_account"      │   │   retry 3x, espera 1s / 2s / 4s      │
  │ espelho local consultável  │   │   translateMetaError (20+ códigos)   │
  └────────────────────────────┘   └────────────┬─────────────────────────┘
                                                │ HTTPS
                                   ┌────────────┴─────────────────────────┐
                                   │ Meta Graph API v25.0                 │
                                   │ graph.facebook.com                   │
                                   └──────────────────────────────────────┘

Decisões não óbvias.

  • O token da Meta mora no banco, cifrado por organização — não em variável de ambiente. É a decisão que torna o BB multi-tenant de verdade: cada organização conecta o próprio Business Manager, e o META_ACCOUNT_CREDENTIAL_MASTER_KEY é apenas a chave de cifragem AES-256-GCM, comum ao serviço. O trade-off é que a chave mestra vira ativo crítico — vazá-la expõe os tokens de todos os tenants — e por isso ela é validada como 64 caracteres hexadecimais e guardada em SOPS.
  • O segredo nunca volta na resposta. BusinessService e AppService passam todo retorno por toSafeBusiness/toSafeApp, que removem o campo cifrado e o substituem por hasAccessToken/hasAppSecret booleanos. Um GET no negócio não vaza token nem por acidente de serialização.
  • Retry só no que adianta repetir. O MetaGraphClient repete três vezes, com espera de 1s, 2s e 4s, apenas quando o erro traduzido é INTERNAL, SERVICE_UNAVAILABLE ou TIMEOUT. Token inválido, permissão faltando e parâmetro errado não são repetidos, porque nenhum deles melhora com insistência — e insistir em erro de autenticação é a receita para ser bloqueado por limite de taxa.
  • A introspecção de token usa o próprio token como credencial de aplicativo. debugToken(input, input) — a Meta aceita que um token se autovalide, o que evita exigir um token de aplicativo separado só para checar saúde. Detalhe operacional importante: token inválido retorna HTTP 200 com is_valid: false, não 401. Quem integrar direto precisa checar o campo, não o status.
  • Os identificadores das rotas são UUIDs internos, não IDs da Meta. Todas as rotas passam por validateUuidParam. GET /wabas/:id recebe o UUID da linha em meta_wabas, não o identificador numérico da WABA na Meta. Os campos metaAppId e metaWabaId nos corpos de configuração de webhook também são UUIDs internos, apesar do nome sugerir o contrário — é a armadilha de integração mais provável deste BB.
  • A Meta é a fonte de verdade; o banco é cache consultável. Por isso existe detectDrift: em vez de fingir que o espelho está sempre correto, o BB expõe a diferença. A reconciliação usa upsertByMetaId mais exclusão lógica de quem sumiu do lado da Meta, o que torna discover e sync seguros de repetir.
  • O nível de webhook por número não existe porque a Meta não o oferece. O enum MetaWebhookLevel tem o valor PHONE_NUMBER, mas não há endpoint correspondente na Meta: os eventos trafegam pela WABA e são distinguidos por metadata.phone_number_id. O roteamento por número é responsabilidade da aplicação consumidora.

Monolito vs. standalone. O app.ts é montado no monolito em src/app.ts e responde em http://localhost:3000/meta-account. O main.ts sobe o mesmo app com Bun.serve na porta 3028 quando DEPLOYMENT_MODE=standalone. Não há diferença de comportamento entre os modos: o BB não chama outros building blocks, só a Graph API. O que muda é que, em standalone, applyCommonMiddleware é a única fonte de limite de corpo, CORS, cabeçalhos de segurança e limite de taxa — no monolito eles também vêm da camada externa.


08Conceitos e modelo de dados

Glossário

TermoSignifica
Business Manager (BM)Também chamado de business portfolio. O contêiner de tudo que uma empresa tem na Meta: contas de anúncio, páginas, aplicativos, catálogos e WABAs. É a raiz do grafo. No BB, corresponde ao modelo MetaBusiness. Quem é dono do BM é dono dos ativos.
WABAWhatsApp Business Account. A conta que agrupa números de telefone e templates de mensagem. Não é o número: uma WABA tem vários números. É o ativo cuja propriedade determina o aprisionamento discutido na §5.
WABA própria vs. de clienteA Meta expõe duas coleções distintas no BM: owned_whatsapp_business_accounts e client_whatsapp_business_accounts. O BB lê as duas e grava a origem em ownershipType (owned ou client). Uma WABA "de cliente" é uma conta de terceiro compartilhada com o seu BM.
App IDIdentificador do aplicativo Meta (criado em developers.facebook.com) que faz as chamadas de API. É o aplicativo, não a empresa. Um BM pode ter vários.
App SecretSegredo do aplicativo. Combinado ao App ID na forma `{appId}
System UserUsuário não-humano dentro do BM, com papel ADMIN ou EMPLOYEE. É o dono correto de um token de integração. A Meta não oferece exclusão de system user pela API — criar é definitivo.
Token de system user vs. de usuárioO token gerado a partir de um system user pode ser configurado para não expirar, e é o recomendado para integração. O token obtido pelo painel do desenvolvedor ou por sessão de usuário expira — tipicamente em cerca de 60 dias, e é a causa do erro 190/463. O BB gera o primeiro tipo em POST /system-users/:id/generate-token, com set_token_expires_in_60_days=false.
Escopo (scope)Permissão do token na Meta. O BB exige business_management e whatsapp_business_management (constante REQUIRED_SCOPES) e recomenda somar whatsapp_business_messaging e catalog_management (RECOMMENDED_SCOPES). Sem os dois obrigatórios, POST /discover recusa a chamada antes de gravar qualquer coisa.
Embedded SignupFluxo hospedado pela Meta em que o cliente final autentica, aceita termos, escolhe ou cria BM e WABA, informa o número e define o nome de exibição; ao final devolve o identificador da WABA, o do número e um código trocável por token. Não é implementado por este BB — ver §15.
Verificação de negócioBusiness verification. Processo em que a Meta valida a existência legal da empresa. Destrava limites: eleva o teto de números registrados do portfólio de 2 para 20 e é um dos caminhos para sair do primeiro tier de mensagens.
Nome de exibiçãoDisplay name. O nome que o destinatário vê. Passa por aprovação da Meta e precisa estar com name_status igual a APPROVED para operações como migração entre parceiros.
Limite de mensagens (tier)Quantos usuários únicos você pode iniciar conversa com, por período. Desde 7 de outubro de 2025 os tiers são 250 / 2.000 / 10.000 / 100.000 / ilimitado e são calculados por business portfolio, não mais por número (Upcoming changes to messaging limits). O tier de 1.000 deixou de existir.
Qualidade do númeroA Meta atribui ao número uma avaliação de qualidade que acompanha o status e o limite de mensagens. O BB grava o valor bruto retornado pela API em MetaPhoneNumber.qualityRating, sem reinterpretá-lo.
Divergência (drift)Diferença entre o espelho local e o estado atual na Meta. GET /sync/drift compara e devolve campo a campo.

Modelo de dados — schema meta_account no PostgreSQL. 11 modelos.

Modelo PrismaTabelaPropósitoCampos-chave
MetaBusinessmeta_businessesO Business Manager conectado; raiz do grafometaBusinessId, accessToken (cifrado), verificationStatus, lastSyncStatus
MetaWabameta_wabasConta de WhatsApp BusinessmetaWabaId, ownershipType, businessVerificationStatus, accountReviewStatus, currency
MetaPhoneNumbermeta_phone_numbersNúmero dentro de uma WABAmetaPhoneNumberId, displayPhoneNumber, qualityRating, codeVerificationStatus, nameStatus, throughputLevel, isPinEnabled
MetaAppmeta_appsAplicativo Meta do BMmetaAppId, namespace, appSecret (cifrado, opcional)
MetaSystemUsermeta_system_usersUsuário de sistema do BMmetaSystemUserId, role
MetaCatalogmeta_catalogsCatálogo de produtosmetaCatalogId, productCount
MetaProductmeta_productsProduto dentro de um catálogometaProductId, retailerId, price, currency, availability
MetaTemplateSnapshotmeta_template_snapshotsRetrato de um template da WABAmetaTemplateId, language, category, status, qualityScore, components
MetaWebhookConfigmeta_webhook_configsConfiguração de webhook registradalevel, callbackUrl, verifyToken, subscribedFields, overrideCallbackUri
MetaTokenHealthmeta_token_healthSaúde do token do negócio (1 por negócio)status, tokenType, scopes, missingScopes, expiresAt, lastCheckedAt
MetaSyncLogmeta_sync_logsHistórico de sincronizaçõessyncType, status, resourcesFound, resourcesSynced, durationMs

Todos os modelos com dado da Meta guardam a resposta original em rawMeta (JSON) e o instante da última leitura em lastSyncAt. Todos usam exclusão lógica por deletedAt. Todos são isolados por organizationId, com unicidade composta — por exemplo @@unique([organizationId, metaWabaId]) — de modo que duas organizações podem apontar para o mesmo recurso da Meta sem colidir.

Enumerações

EnumValores
MetaSyncStatusPENDING · IN_PROGRESS · COMPLETED · FAILED
MetaWebhookLevelAPP · WABA · PHONE_NUMBER (este último declarado, não usado — ver §15)
MetaTokenStatusVALID · EXPIRING_SOON · EXPIRED · INVALID · MISSING_SCOPES

Grafo de recursos

  MetaBusiness  (1 Business Manager por organização conectada)
    │
    ├── MetaWaba [owned | client]
    │     ├── MetaPhoneNumber        quality, status, verificação, throughput
    │     ├── MetaTemplateSnapshot   único por (org, template, idioma)
    │     └── MetaWebhookConfig      nível WABA
    │
    ├── MetaApp ────────────────────▶ MetaWebhookConfig   nível APP
    │     └── appSecret cifrado (necessário para webhook de app)
    │
    ├── MetaSystemUser              origem dos tokens permanentes
    ├── MetaCatalog
    │     └── MetaProduct
    ├── MetaTokenHealth             1:1 com o negócio
    └── MetaSyncLog                 histórico

Máquina de estados da saúde do token

                    POST /token/check  (ou POST /discover)
                              │
                              ▼
                   debugToken na Graph API
                              │
              ┌───────────────┴───────────────┐
        is_valid = false                is_valid = true
              │                                │
              ▼                                ▼
         ┌─────────┐              expires_at existe e > 0 ?
         │ INVALID │                           │
         └─────────┘         ┌─────────────────┴──────────────┐
                            sim                              não
                             │                                │
              ┌──────────────┼───────────────┐                │
        já passou      < 7 dias        > 7 dias               │
              │              │               │                │
              ▼              ▼               └────────┬───────┘
        ┌─────────┐  ┌───────────────┐                │
        │ EXPIRED │  │ EXPIRING_SOON │       faltam escopos obrigatórios?
        └─────────┘  └───────────────┘                │
                                            ┌─────────┴─────────┐
                                          sim                  não
                                            │                    │
                                            ▼                    ▼
                                  ┌─────────────────┐      ┌───────┐
                                  │ MISSING_SCOPES  │      │ VALID │
                                  └─────────────────┘      └───────┘

  INVALID, EXPIRED e EXPIRING_SOON publicam meta-account.token.healthChanged.
  Token de system user sem expiração chega com expires_at = 0 e nunca vira EXPIRING_SOON.

09Referência da API

Prefixo: /meta-account. Em monolito, a base é http://localhost:3000. Em standalone, a porta é 3028.

Todas as 36 rotas exigem, sem exceção: authMiddleware (Bearer JWT do IAM), requirePermission e o middleware local requireOrganization — que devolve 403 quando o token não carrega organizationId. Rotas de leitura exigem META_ACCOUNT_READ; rotas que escrevem ou chamam a Meta exigem META_ACCOUNT_MANAGE.

Armadilha central: todo :id, :businessId, :catalogId e :productId é o UUID interno da linha no banco, validado por validateUuidParam. Não é o identificador numérico da Meta. Os campos metaAppId e metaWabaId nos corpos de webhook também são UUIDs internos, apesar do nome.

Descoberta — /meta-account/api/v1/discover

MétodoRotaDescriçãoPermissão
POST/meta-account/api/v1/discoverValida o token e mapeia o portfólio Meta inteiroMETA_ACCOUNT_MANAGE

Negócios — /meta-account/api/v1/businesses

MétodoRotaDescriçãoPermissão
GET/meta-account/api/v1/businessesLista os Business Managers conectadosMETA_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:idBusca um negócio (sem o token)META_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:id/graphGrafo completo: WABAs, números, templates, webhooks, apps, system users, catálogos e saúde do tokenMETA_ACCOUNT_READ
PATCH/meta-account/api/v1/businesses/:idAtualiza nome e/ou rotaciona o access tokenMETA_ACCOUNT_MANAGE
DELETE/meta-account/api/v1/businesses/:idExclusão lógica do negócio. Responde 204META_ACCOUNT_MANAGE

WABAs — /meta-account/api/v1/wabas

MétodoRotaDescriçãoPermissão
GET/meta-account/api/v1/wabas/:idBusca uma WABAMETA_ACCOUNT_READ
GET/meta-account/api/v1/wabas/:id/phone-numbersWABA com os números aninhadosMETA_ACCOUNT_READ
GET/meta-account/api/v1/wabas/:id/templatesWABA com os retratos de template aninhadosMETA_ACCOUNT_READ

Números — /meta-account/api/v1/phone-numbers

MétodoRotaDescriçãoPermissão
GET/meta-account/api/v1/phone-numbers/:idBusca um númeroMETA_ACCOUNT_READ
POST/meta-account/api/v1/phone-numbers/:id/request-verificationPede código de verificação por SMS ou VOICEMETA_ACCOUNT_MANAGE
POST/meta-account/api/v1/phone-numbers/:id/verifyConfirma o código recebidoMETA_ACCOUNT_MANAGE

Templates, aplicativos e system users

MétodoRotaDescriçãoPermissão
GET/meta-account/api/v1/templates/:idBusca um retrato de templateMETA_ACCOUNT_READ
PATCH/meta-account/api/v1/apps/:idGrava o appSecret cifrado do aplicativoMETA_ACCOUNT_MANAGE
POST/meta-account/api/v1/system-users/:id/generate-tokenGera token de system user (sem expiração)META_ACCOUNT_MANAGE
GET/meta-account/api/v1/businesses/:businessId/system-usersLista os system users do negócioMETA_ACCOUNT_READ
POST/meta-account/api/v1/businesses/:businessId/system-usersCria system user na Meta e grava. Responde 201META_ACCOUNT_MANAGE

Webhooks

MétodoRotaDescriçãoPermissão
POST/meta-account/api/v1/webhooks/appAssina o webhook no nível do aplicativo. Responde 201META_ACCOUNT_MANAGE
POST/meta-account/api/v1/webhooks/wabaAssina o webhook no nível da WABA. Responde 201META_ACCOUNT_MANAGE
DELETE/meta-account/api/v1/webhooks/:idExclusão lógica da configuração. Responde 204META_ACCOUNT_MANAGE
GET/meta-account/api/v1/businesses/:businessId/webhooksLista as configurações registradas da organizaçãoMETA_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:businessId/webhooks/statusConsulta a Meta ao vivo e devolve o estado por nívelMETA_ACCOUNT_READ
POST/meta-account/api/v1/businesses/:businessId/webhooks/auto-configureAssina todos os apps com segredo e todas as WABAsMETA_ACCOUNT_MANAGE

Catálogos e produtos

MétodoRotaDescriçãoPermissão
GET/meta-account/api/v1/businesses/:businessId/catalogsLista os catálogos do negócioMETA_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/productsLista os produtos do catálogoMETA_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/:productIdBusca um produtoMETA_ACCOUNT_READ
POST/meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/productsCria produto na Meta e grava. Responde 201META_ACCOUNT_MANAGE
PATCH/meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/:productIdAtualiza produto na Meta e no espelhoMETA_ACCOUNT_MANAGE
DELETE/meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/:productIdRemove na Meta e exclui logicamente. Responde 204META_ACCOUNT_MANAGE
POST/meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/syncReimporta os produtos do catálogo a partir da MetaMETA_ACCOUNT_MANAGE

Sincronização e saúde do token

MétodoRotaDescriçãoPermissão
POST/meta-account/api/v1/businesses/:businessId/syncSincronização completaMETA_ACCOUNT_MANAGE
POST/meta-account/api/v1/businesses/:businessId/sync/:resourceTypeSincronização de um tipo sóMETA_ACCOUNT_MANAGE
GET/meta-account/api/v1/businesses/:businessId/sync/historyHistórico de sincronizaçõesMETA_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:businessId/sync/driftCompara espelho local com a MetaMETA_ACCOUNT_READ
GET/meta-account/api/v1/businesses/:businessId/token/healthResumo da saúde do token, com orientaçãoMETA_ACCOUNT_READ
POST/meta-account/api/v1/businesses/:businessId/token/checkReconsulta a Meta e regrava a saúdeMETA_ACCOUNT_MANAGE

Valores aceitos em :resourceType: wabas, phone_numbers, templates, apps, system_users, catalogs. Qualquer outro devolve 400.

Saúde do serviço

MétodoRotaDescrição
GET/meta-account/healthSonda de disponibilidade do serviço. Pública, não contabilizada nos 36 endpoints

POST /meta-account/api/v1/discover

O ponto de entrada do BB. Valida o token, confere os escopos obrigatórios antes de gravar qualquer coisa, e só então percorre o grafo.

Request

{
  "businessId": "1234567890123456",
  "accessToken": "<token de system user do SEU Business Manager>"
}
CampoTipoObrigatórioDescrição
businessIdstringSimO identificador numérico do seu Business Manager na Meta — não um UUID
accessTokenstringSimToken com, no mínimo, business_management e whatsapp_business_management

Resposta 201

{
  "data": {
    "business": {
      "id": "3f1c…",
      "metaBusinessId": "1234567890123456",
      "name": "Empresa Exemplo LTDA",
      "verificationStatus": "verified"
    },
    "tokenHealth": {
      "status": "VALID",
      "scopes": ["business_management", "whatsapp_business_management", "..."],
      "missingScopes": [],
      "expiresAt": null
    },
    "wabas": [
      {
        "waba": { "id": "9a2e…", "metaWabaId": "9876543210", "name": "Atendimento", "ownershipType": "owned" },
        "phoneNumbers": [
          { "id": "b71d…", "metaPhoneNumberId": "5544332211", "displayPhoneNumber": "+55 11 90000-0000",
            "qualityRating": "GREEN", "status": "CONNECTED" }
        ],
        "templateCount": 12
      }
    ],
    "apps": [{ "id": "c40a…", "metaAppId": "111222333444", "name": "Integração Exemplo" }],
    "systemUsers": [{ "id": "d55b…", "metaSystemUserId": "555666777", "name": "automacao", "role": "ADMIN" }],
    "catalogs": [{ "id": "e66c…", "metaCatalogId": "888999000", "name": "Catálogo Loja", "productCount": 42 }],
    "syncLog": { "status": "COMPLETED", "resourcesFound": 61, "resourcesSynced": 61, "durationMs": 4312 }
  }
}

expiresAt: null indica token sem expiração — é o comportamento esperado de um token de system user e o estado desejável.

Erros

StatusQuando
400VALIDATION — token inválido segundo a Meta, ou faltando escopo obrigatório. O corpo traz details.missingScopes e details.currentScopes
401Token da Meta expirado ou revogado; a mensagem já vem traduzida com orientação
403Sem META_ACCOUNT_MANAGE, ou token do IAM sem organizationId
503Limite de taxa da Meta atingido, após as três tentativas

É idempotente: chamar de novo com o mesmo businessId atualiza o registro existente, graças ao upsertByMetaId.


GET /meta-account/api/v1/businesses/:businessId/token/health

Lê o último estado gravado — não consulta a Meta. Para reconsultar, use POST .../token/check antes.

Resposta 200

{
  "data": {
    "status": "EXPIRING_SOON",
    "message": "Token is valid but expiring soon.",
    "guidance": "Generate a new system user token in Meta Business Settings > System Users for permanent access.",
    "expiresAt": "2026-08-22T10:00:00.000Z",
    "expiresInDays": 6,
    "scopes": ["business_management", "whatsapp_business_management"],
    "missingScopes": ["whatsapp_business_messaging", "catalog_management"],
    "requiredScopes": ["business_management", "whatsapp_business_management",
                       "whatsapp_business_messaging", "catalog_management"]
  }
}

missingScopes compara contra os escopos recomendados, não só os obrigatórios. Um token com status: VALID e missingScopes preenchido está funcional — apenas sem acesso a mensageria ou catálogo. requiredScopes na resposta lista, na verdade, o conjunto recomendado completo.

Responde 404 quando nunca houve verificação para esse negócio; rode POST .../token/check primeiro.


POST /meta-account/api/v1/webhooks/app

Assina o webhook no nível do aplicativo. Exige que o appSecret já esteja gravado por PATCH /apps/:id — sem ele, responde 400.

Request

{
  "metaAppId": "c40a1e88-0000-0000-0000-000000000000",
  "callbackUrl": "https://api.suaempresa.com.br/webhooks/whatsapp",
  "verifyToken": "uma-string-de-no-minimo-8-caracteres",
  "objectType": "whatsapp_business_account",
  "fields": ["messages", "message_template_status_update"]
}
CampoTipoObrigatórioDescrição
metaAppIdstring (UUID)SimUUID interno da linha em meta_apps — não o App ID da Meta
callbackUrlstring (URL)SimA Meta faz verificação real desta URL, com validação de TLS
verifyTokenstringSimMínimo de 8 caracteres
objectTypestringNãoPadrão whatsapp_business_account
fieldsstring[]SimAo menos um campo

GET /meta-account/api/v1/businesses/:businessId/sync/drift

Resposta 200

{
  "data": {
    "checkedAt": "2026-08-16T14:03:11.000Z",
    "drifts": [
      { "resourceType": "phone_number", "resourceId": "5544332211",
        "displayName": "+55 11 90000-0000", "field": "qualityRating",
        "localValue": "GREEN", "remoteValue": "YELLOW" }
    ],
    "summary": { "total": 1, "phone_number": 1 }
  }
}

A comparação cobre, hoje, quatro campos: name e businessVerificationStatus na WABA; qualityRating e status no número. E percorre apenas WABAs próprias — as de cliente ficam de fora. Ver §15.


10Início rápido

Do zero ao portfólio Meta mapeado. O caminho abaixo usa o monolito local, porque o meta-account ainda não está publicado em staging (§1).

Estes comandos não foram executados na redação deste documento — a etapa 3 em diante exige credenciais reais de um Business Manager, que não entram em documentação. As respostas mostradas refletem os tipos de retorno do código. Credenciais de teste em AMBIENTES.md.

0. Subir a infra e o serviço

docker-compose up -d postgres redis minio
bun run dev

1. Autenticar no IAM

TOKEN=$(curl -s -X POST http://localhost:3000/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)

2. Gerar o token na Meta — feito por você, no seu Business Manager

Esta etapa acontece fora da Catalisa, e é ela que garante que a conta é sua:

  1. Em business.facebook.com, abra Configurações do negócio → Usuários → Usuários do sistema.
  2. Crie um system user com papel ADMIN (ou use um existente).
  3. Atribua a ele os ativos: a WABA, o aplicativo e o catálogo.
  4. Clique em Gerar novo token, escolha o aplicativo e marque, no mínimo, business_management e whatsapp_business_management. Some whatsapp_business_messaging e catalog_management se for usar mensageria e catálogo.
  5. Copie o token. Anote também o ID do negócio, visível na URL ou em Informações do negócio.

Guarde esse token com SOPS. Ele dá acesso administrativo ao seu Business Manager.

3. Descobrir o portfólio

curl -s -X POST http://localhost:3000/meta-account/api/v1/discover \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "businessId": "SEU_BUSINESS_ID",
    "accessToken": "SEU_TOKEN_DE_SYSTEM_USER"
  }' | jq '.data | {business, tokenHealth, wabas: (.wabas | length)}'
{
  "business": { "id": "3f1c…", "name": "Empresa Exemplo LTDA", "verificationStatus": "verified" },
  "tokenHealth": { "status": "VALID", "missingScopes": [], "expiresAt": null },
  "wabas": 2
}

4. Guardar o UUID interno e ver o grafo

BUSINESS_ID=$(curl -s http://localhost:3000/meta-account/api/v1/businesses \
  -H "Authorization: Bearer $TOKEN" | jq -r '.data[0].id')

curl -s "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/graph" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.business | {name, wabas: [.wabas[].name]}'

5. Confirmar que o token não vaza

curl -s "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID" \
  -H "Authorization: Bearer $TOKEN" | jq '.data | {name, hasAccessToken, accessToken}'
{ "name": "Empresa Exemplo LTDA", "hasAccessToken": true, "accessToken": null }

O campo accessToken não existe na resposta — toSafeBusiness o remove. O que sobra é o booleano.

6. Checar a saúde do token

curl -s -X POST "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/token/check" \
  -H "Authorization: Bearer $TOKEN" | jq '.data | {status, expiresAt, missingScopes}'

11Receitas

Onboarding completo: do zero ao número apto a enviar

Este é o fluxo que a §1 promete. As etapas em cinza acontecem fora do BB, no domínio da Meta, e são justamente as que garantem que a conta é do cliente.

  ┌─ FORA DO BB — no Business Manager do CLIENTE ───────────────────────────────┐
  │                                                                            │
  │  1. Criar o Business Manager          business.facebook.com                │
  │             │                                                              │
  │             ▼                                                              │
  │  2. Verificação de negócio            destrava 20 números e o tier 2.000   │
  │             │                                                              │
  │             ▼                                                              │
  │  3. Criar app Meta + vincular ao BM   developers.facebook.com              │
  │             │                                                              │
  │             ▼                                                              │
  │  4. Criar WABA + adicionar número     nome de exibição vai para aprovação  │
  │             │                                                              │
  │             ▼                                                              │
  │  5. System user ADMIN + atribuir      gerar token com os escopos           │
  │     ativos (WABA, app, catálogo)      → token NÃO expira                   │
  └─────────────┬──────────────────────────────────────────────────────────────┘
                │  businessId + accessToken
                ▼
  ┌─ NO META-ACCOUNT ──────────────────────────────────────────────────────────┐
  │                                                                            │
  │  6. POST /discover                    valida escopos, mapeia e espelha     │
  │             │                          tudo: WABAs, números, templates,    │
  │             │                          apps, system users, catálogos       │
  │             ▼                                                              │
  │  7. GET  /businesses/:id/token/health  status VALID? expiresAt null?       │
  │             │                                                              │
  │             ▼                                                              │
  │  8. Número ainda não verificado?                                           │
  │     POST /phone-numbers/:id/request-verification  { "method": "SMS" }      │
  │     POST /phone-numbers/:id/verify                { "code": "123456" }     │
  │             │                                                              │
  │             ▼                                                              │
  │  9. PATCH /apps/:id  { "appSecret": "…" }   habilita webhook de app        │
  │             │                                                              │
  │             ▼                                                              │
  │ 10. POST /businesses/:id/webhooks/auto-configure                           │
  │             │                          assina app + todas as WABAs         │
  │             ▼                                                              │
  │ 11. GET  /businesses/:id/webhooks/status    conferir antes de seguir       │
  └─────────────┬──────────────────────────────────────────────────────────────┘
                │  wabaId + businessId + accessToken (IDs da META, não UUIDs)
                ▼
  ┌─ NO WPP-BUSINESS ──────────────────────────────────────────────────────────┐
  │ 12. POST /wpp-business/api/v1/accounts                                     │
  │             │                                                              │
  │             ▼                                                              │
  │ 13. POST /wpp-business/api/v1/accounts/:id/phone-numbers                   │
  │             │                                                              │
  │             ▼                                                              │
  │     ✅ número apto a enviar — começando no tier de 250 destinatários/dia   │
  └────────────────────────────────────────────────────────────────────────────┘

Armadilhas.

  • As etapas 1 a 5 não são automatizadas por este BB. Não existe Embedded Signup aqui (§15). É trabalho manual do cliente, uma única vez — e é o preço da propriedade da conta.
  • Gere o token como system user, não pelo painel do desenvolvedor. O token do painel expira em cerca de 60 dias e produz o erro 190/463 num sábado.
  • A callbackUrl do webhook precisa ser pública e com TLS válido. A Meta faz uma requisição real de verificação; URL de exemplo ou certificado inválido faz a assinatura falhar.
  • Na etapa 12, o wpp-business espera os identificadores da Meta (wabaId, businessId), não os UUIDs internos do meta-account. Leia-os dos campos metaWabaId e metaBusinessId.
  • Portfólio novo começa limitado a 2 números registrados e sobe para 20 com a verificação de negócio, ou ao atingir o limite de 2.000 mensagens.

Conferir e consertar webhook que parou de entregar

# 1. O que a Meta diz que está inscrito, agora
curl -s "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/webhooks/status" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.levels'

# 2. Reaplicar em todos os apps com segredo e todas as WABAs
curl -s -X POST \
  "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/webhooks/auto-configure" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"callbackBaseUrl":"https://api.suaempresa.com.br/webhooks/whatsapp"}' | jq '.data.results'

A resposta traz um item por recurso, com success e error:

[
  { "resource": "app:111222333444", "success": true },
  { "resource": "waba:9876543210",  "success": true },
  { "resource": "waba:1122334455",  "success": false, "error": "Meta API bad request: …" }
]

Armadilhas.

  • auto-configure só toca aplicativos com appSecret gravado. Um aplicativo sem segredo é silenciosamente ignorado — rode PATCH /apps/:id antes e confira o hasAppSecret.
  • A operação é parcialmente tolerante a falha por desenho: uma WABA que falha não aborta as outras. Sempre leia o array results; um 200 não significa que tudo deu certo.
  • Ela usa um conjunto fixo de campos: messages, message_template_status_update, message_template_quality_update e account_update. Se precisar de outros, use POST /webhooks/app com a lista explícita.
  • O verifyToken é gerado aleatoriamente a cada chamada. Se o seu endpoint valida um token fixo, use as rotas específicas em vez desta.

Verificar um número novo

curl -s -X POST \
  "http://localhost:3000/meta-account/api/v1/phone-numbers/$PHONE_UUID/request-verification" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"method":"SMS","language":"pt_BR"}'

curl -s -X POST "http://localhost:3000/meta-account/api/v1/phone-numbers/$PHONE_UUID/verify" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"code":"123456"}'

Armadilhas. O $PHONE_UUID é o UUID interno, e o BB resolve sozinho a cadeia número → WABA → negócio para achar o token. O código aceita de 4 a 10 caracteres. Números de teste da Meta se comportam de forma diferente dos reais — valide o fluxo com um número de teste antes de mexer em produção.

Descobrir o que mudou na Meta sem você saber

curl -s "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/sync/drift" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.summary, .data.drifts'

# Reconciliar tudo
curl -s -X POST "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/sync" \
  -H "Authorization: Bearer $TOKEN" | jq '.data | {created, updated, removed}'

# Ou só um tipo
curl -s -X POST \
  "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/sync/phone_numbers" \
  -H "Authorization: Bearer $TOKEN" | jq '.data'

Armadilhas. drift não é exaustivo — cobre quatro campos e só WABAs próprias (§15). Um relatório vazio não prova que nada mudou. O removed do sync é exclusão lógica: o recurso sai das listagens mas a linha permanece para auditoria. E não há agendamento embutido: chame o sync por cron ou por um job seu.

Rotacionar o token da Meta sem downtime

# Depois de gerar o novo token no Business Manager
curl -s -X PATCH "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"accessToken":"NOVO_TOKEN"}' | jq '.data.hasAccessToken'

curl -s -X POST "http://localhost:3000/meta-account/api/v1/businesses/$BUSINESS_ID/token/check" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.status'

Armadilhas. Gere e valide o token novo antes de revogar o antigo — o PATCH sobrescreve na hora, e um token errado só aparece na próxima chamada à Meta. Rode token/check logo em seguida. Se o wpp-business usa uma cópia do mesmo token, atualize-a também: os dois building blocks guardam credenciais independentes.


12Integração com outros building blocks

Building blockComo se relacionaObrigatório
IAMEmite o JWT; o organizationId do token é o tenant, e as permissões META_ACCOUNT_* são verificadas em toda rotaSim
wpp-businessO consumidor natural. Recebe wabaId, businessId e accessToken — os três valores que o meta-account descobre e valida — para criar a conta que envia mensagemNão, mas é o par esperado
wppBuilding block de WhatsApp multi-driver; a camada Meta oficial é provisionada aquiNão
Webhooks EnginePode receber e redistribuir os eventos de domínio (meta-account.token.expiring, meta-account.sync.driftDetected) para o clienteNão
Audit TrailRegistra quem rotacionou token, quem alterou webhook, quem removeu negócioNão

A relação com o wpp-business, em uma imagem. É a divisão que dá nome ao BB: um cuida da conta, o outro da conversa.

   ┌──────────────────────────────────────────────────────────────────────┐
   │                        meta-account                                  │
   │                    "a conta é sua e está saudável"                   │
   │                                                                      │
   │   descoberta · espelho · saúde do token · webhooks · divergência     │
   └───────────────────────────────┬──────────────────────────────────────┘
                                   │
                 entrega três valores, lidos do espelho:
                   metaWabaId · metaBusinessId · accessToken
                                   │
                                   ▼
   ┌──────────────────────────────────────────────────────────────────────┐
   │                        wpp-business                                  │
   │                    "a conversa acontece"                             │
   │                                                                      │
   │   POST /wpp-business/api/v1/accounts                                 │
   │        { wabaId, businessId, accessToken, name }                     │
   │                                                                      │
   │   mensagens · templates · campanhas · flows · inbox · catálogo       │
   │   contatos · automações · pagamentos · analytics                     │
   └───────────────────────────────┬──────────────────────────────────────┘
                                   │  eventos de domínio
                                   ▼
   ┌──────────────────┐   ┌──────────────────┐   ┌───────────────────────┐
   │  webhooks-engine │   │   audit-trail    │   │       customers       │
   │  entrega ao      │   │   quem mexeu     │   │  quem é a pessoa do   │
   │  sistema cliente │   │   no quê         │   │  outro lado           │
   └──────────────────┘   └──────────────────┘   └───────────────────────┘

Honestidade sobre o acoplamento. Hoje a passagem de bastão é operacional, não automática: não há código no wpp-business que importe o meta-account, nem chamada de módulo entre os dois. Quem faz a ponte é o painel ou o script de provisionamento, lendo os identificadores de um e escrevendo no outro. Isso é bom para desacoplamento e ruim para conveniência — os dois guardam cópias independentes do token, e rotacionar exige atualizar os dois. Uma etapa de handoff automática é candidata natural de roadmap.

O argumento comercial continua de pé, e é o mesmo da §5: em um BSP, essas duas camadas são a mesma caixa-preta, e é por isso que sair dela custa caro. Aqui elas são separadas de propósito, e a de baixo — a que determina a propriedade do ativo — fica na mão do cliente.


13Configuração e operação

Variáveis de ambiente

VariávelDescriçãoObrigatóriaPadrão
META_ACCOUNT_CREDENTIAL_MASTER_KEYChave AES-256-GCM que cifra tokens e segredos no banco. 64 caracteres hexadecimais (32 bytes). Gere com openssl rand -hex 32Sim, para usar o módulo
MODULE_META_ACCOUNT_PORTPorta no modo standaloneNão3028
MODULE_META_ACCOUNT_URLURL do módulo em standalone; vazio significa localNão''
META_ACCOUNT_WEBHOOK_BASE_URLDeclarada em src/shared/config/env.ts, mas nenhum código a lê hoje. A URL de webhook vem sempre do corpo da requisiçãoNão
DATABASE_URLPostgreSQLSim
REDIS_URLRedis, usado pelo limite de taxa comumSim
DEPLOYMENT_MODEmonolith ou standaloneNãomonolith

A chave mestra é validada no schema de configuração como exatamente 64 caracteres hexadecimais, e getMasterKey() a relê de process.env a cada operação, rejeitando formato inválido. Sem ela, cifrar e decifrar lançam erro — o módulo carrega, mas nenhuma operação com credencial funciona.

Dependências de infraestrutura

DependênciaPara quê
PostgreSQLSchema meta_account, 11 tabelas
RedisContadores do rateLimitMiddleware aplicado por applyCommonMiddleware
Meta Graph API v25.0https://graph.facebook.com/v25.0 — a única integração externa

Limites e quotas

LimiteValorOrigem
Corpo da requisição1 MBapplyCommonMiddleware
Tentativas contra a Meta3, com espera de 1s, 2s e 4sMetaGraphClient
Itens por consulta à Meta100, fixo, sem paginação de continuaçãolimit=100 nas 8 consultas de listagem
Janela de alerta de expiração7 diasTokenHealthService
Limite de taxa da Meta200 × usuários ativos diários por horaMeta; monitore o cabeçalho X-Business-Use-Case-Usage
Números por portfólio novo2, subindo para 20 com verificação de negócioMeta
Tier inicial de mensagens250 destinatários únicos, por business portfolioMeta, desde 2025-10-07

Catálogo de erros

Erros da plataforma:

StatusCódigoSignificaO que fazer
400VALIDATIONCorpo reprovado no Zod, ou resourceType desconhecidoConfira campos e tipos contra a §9
400VALIDATIONToken da Meta inválido ou sem escopo obrigatório, em POST /discoverLeia details.missingScopes e gere token novo com os escopos
400VALIDATIONappSecret ausente ao assinar webhook de aplicativoRode PATCH /apps/:id antes
401UNAUTHORIZEDJWT do IAM ausente ou expiradoRenove no IAM
403FORBIDDENSem META_ACCOUNT_READ/META_ACCOUNT_MANAGE, ou token sem organizationIdConfira a permissão e o contexto de organização
404NOT_FOUNDUUID inexistente, de outra organização, ou logicamente excluídoConfira se está usando o UUID interno, não o ID da Meta
500INTERNALFalha ao decifrar o token — normalmente chave mestra trocadaVerifique META_ACCOUNT_CREDENTIAL_MASTER_KEY

Erros da Meta, já traduzidos por translateMetaError, com o objeto original preservado em details.meta:

Código MetaSubcódigoViraO que fazer
190458401Aplicativo não instalado — vincule o app ao Business Manager
190459401Checkpoint de segurança — entre na Meta e resolva
190460401Senha alterada desde a emissão — gere token novo
190463401Token expirado. Troque por token de system user
190467401Token inválido ou revogado — gere outro em System Users
190492401Sessão alterada — autentique de novo
102401Sessão da API expirada
10403Escopo faltando no token
200299403Permissões insuficientes — revise os ativos atribuídos ao system user
4, 17, 32503Limite de taxa da Meta — espere alguns minutos
8000080099503Limite de taxa do WhatsApp — reduza a vazão
100400Parâmetro inválido; a mensagem original vem junto
130429503Limite de taxa da Cloud API para aquele número
131005403Número não registrado, ou falta whatsapp_business_messaging
131016503Indisponibilidade temporária do WhatsApp
131048503Restrição por taxa de spam
1 / 2500 / 503Erro interno da Meta — repita em alguns segundos

Observabilidade

  • Eventos de domínio publicados: business.discovered, business.updated, business.deleted, token.healthChanged, sync.completed, webhook.configured, webhook.removed — todos com prefixo meta-account.. As constantes token.expiring, sync.started, sync.failed e sync.driftDetected estão declaradas mas não publicadas por nenhum código hoje (§15).
  • MetaSyncLog é a trilha operacional: tipo, status, recursos encontrados e sincronizados, duração em milissegundos. GET /sync/history a expõe. É a primeira coisa a olhar quando alguém diz que "o espelho está errado".
  • MetaTokenHealth.lastCheckedAt diz há quanto tempo a saúde não é reavaliada. Um valor antigo significa que ninguém está rodando token/check — o BB não verifica sozinho.
  • rawMeta guarda a resposta original da Meta em cada recurso, e details.meta acompanha cada erro traduzido com fbtrace_id. Junto, é o que permite abrir chamado na Meta com evidência.
  • GET /meta-account/health responde com o payload padrão de versão. É uma sonda de processo vivo, não verifica banco nem conectividade com a Meta — diferente do /health do IAM.

14Segurança e compliance

Isolamento entre tenants. O organizationId vem do claim assinado do JWT e nunca do corpo. Todas as 36 rotas aplicam o requireOrganization local, que devolve 403 quando o claim falta. Abaixo disso, a garantia é repetida na camada de dados: todo método de repositório recebe organizationId e o inclui na cláusula wherefindById(id, organizationId) filtra por { id, organizationId, deletedAt: null }. Não existe caminho de leitura que dispense o filtro. As chaves compostas @@unique([organizationId, metaXxxId]) reforçam a separação no schema: duas organizações podem referenciar o mesmo recurso da Meta sem colisão nem vazamento.

Segredos da Meta. O access token do negócio e o app secret são cifrados em AES-256-GCM com vetor de inicialização aleatório de 12 bytes e tag de autenticação, no formato iv:authTag:ciphertext. A chave é a META_ACCOUNT_CREDENTIAL_MASTER_KEY, de 32 bytes, exigida em hexadecimal e validada no formato. O GCM garante que um valor adulterado no banco falhe a decifragem em vez de produzir texto claro corrompido.

Segredo não sai na resposta. toSafeBusiness e toSafeApp desestruturam e descartam os campos cifrados, devolvendo hasAccessToken e hasAppSecret booleanos. getResourceGraph faz o mesmo descarte antes de serializar o grafo. A decifragem acontece só no momento da chamada à Meta, em variável local, e o valor não é registrado em log.

Uma exceção deliberada e importante: POST /system-users/:id/generate-token retorna o token gerado em texto claro, uma única vez — não há outra forma de entregá-lo a quem pediu. Trate a resposta dessa rota como material sensível: não a registre em log de aplicação, não a exiba em tela sem mascaramento, e guarde com SOPS. Exija META_ACCOUNT_MANAGE para pouca gente.

Superfície do token da Meta. O token gravado costuma ser de system user com papel ADMIN — uma credencial ampla sobre o Business Manager do cliente. Duas consequências operacionais: a chave mestra é um ativo de altíssimo valor, porque comprometê-la expõe os tokens de todos os tenants; e o princípio de menor privilégio deve ser aplicado na Meta, atribuindo ao system user apenas os ativos necessários, já que o BB não tem como reduzir o escopo de um token que recebe pronto.

Validação antes de gravar. POST /discover introspecta o token e recusa a operação quando faltam escopos obrigatórios, antes de persistir qualquer coisa. Credencial insuficiente não entra no banco.

Exclusão lógica. Todos os modelos usam deletedAt, e todas as consultas filtram por deletedAt: null. O histórico sobrevive à remoção, o que atende retenção e auditoria.

Proteções de borda. applyCommonMiddleware aplica limite de corpo de 1 MB, CORS com política de falha segura, cabeçalhos de segurança e limite de taxa. Isso importa especialmente em standalone, que é o modo de produção: sem esse helper, o app do módulo subiria sem nenhuma dessas proteções.

LGPD. O BB armazena números de telefone de negócio (displayPhoneNumber, verifiedName) e, quando há catálogo, dados de produto. Números de atendimento empresarial não são, em geral, dado pessoal de titular — mas o campo rawMeta guarda a resposta bruta da Meta, e o conteúdo dela é definido pela Meta, não por nós. Antes de operação regulada, revise o que rawMeta está retendo e por quanto tempo. Não há expurgo automatizado — a exclusão é lógica (§15).

Enquadramento com a Meta. Operar com a WABA no Business Manager do cliente mantém a relação de faturamento entre o cliente e a Meta, e mantém a responsabilidade pelas políticas de mensageria com quem é dono da conta. É a configuração mais simples de defender numa auditoria, além de ser a que preserva a portabilidade discutida na §5.


15Limitações conhecidas

Maturidade

LimitaçãoImpactoSituação
Não publicado em staging nem produçãoNão aparece em infra/TOPOLOGY.md, no docker-compose.yml nem em AMBIENTES.md. Roda em monolito local e tem entry point standalone, mas não há ambiente hospedadoBeta — não prometa data de disponibilidade a cliente
Sem testes unitários e de integraçãoA cobertura automatizada é um único teste ponta a ponta (tests/e2e/meta-account/meta-graph-client.real.test.ts) que bate na API real da Meta e exige credenciaisLacuna reconhecida

Funcionalidade no schema, ausente no código

LimitaçãoImpactoSituação
MetaWaba.messagingLimitTier nunca é preenchidoA coluna existe no Prisma, mas nenhum código escreve nela e a Graph API não é consultada para isso. O tier de mensagens não é visível pelo BBNão implementado — não anuncie monitoramento de tier
MetaWebhookLevel.PHONE_NUMBER declarado e não usadoNenhuma configuração é criada nesse nível. Correto, porque a Meta não oferece assinatura por número — mas o valor no enum sugere o contrárioPor design; o enum é enganoso
MetaWebhookConfig.lastVerifiedAt e MetaTokenHealth.errorMessage nunca populadosSempre nulosNão implementado
META_ACCOUNT_WEBHOOK_BASE_URL declarada e não lidaA variável existe em env.ts e nenhum código a consulta; a URL vem sempre do corpoNão implementado
Eventos declarados e nunca publicadostoken.expiring, sync.started, sync.failed e sync.driftDetected estão em MetaAccountEvents mas nenhum código os emite. Quem assinar esses tópicos não recebe nadaNão implementado

Capacidade sem rota HTTP

LimitaçãoImpactoSituação
Criar e excluir catálogoCatalogService.create e .delete existem e chamam a Meta, mas nenhuma rota os expõe. Só a listagem está publicadaImplementado no serviço, sem rota
Vincular catálogo a WABACatalogService.getWabaCatalogs existe, sem rotaImplementado no serviço, sem rota
Listagens intermediáriasNão há GET /businesses/:id/wabas, GET /wabas/:id/… como coleção própria, GET /apps nem GET /catalogs/:id. Chega-se a eles pelo /graph ou pelos aninhamentosParcial
Detalhes ao vivo da MetagetWabaDetails, getPhoneNumberDetails, getCatalogDetails e getProduct existem no cliente HTTP, sem rota. As leituras servem o espelho, não a MetaImplementado no provider, sem rota
Cancelar assinatura de webhook na MetadeleteAppSubscription e unsubscribeWabaFromApp existem no cliente, mas DELETE /webhooks/:id só faz exclusão lógica local — a assinatura continua ativa na MetaDivergência relevante; confira no painel da Meta

Cobertura funcional

LimitaçãoImpactoSituação
Sem Embedded SignupO onboarding pressupõe que o cliente já gerou um token no Business Manager dele. Não há troca de código por token, nem fluxo hospedado. As etapas 1 a 5 da receita da §11 são manuaisRoadmap — é a maior lacuna de experiência
Sem paginação de continuaçãoAs 8 consultas de listagem usam limit=100 fixo. getWabaTemplates aceita cursor, mas nem DiscoveryService nem SyncService o passam; fetchAllPages existe e não é chamado. Portfólio com mais de 100 templates por WABA é truncado silenciosamenteNão implementado — limite real para operação grande
Detecção de divergência rasaCompara 4 campos (name e businessVerificationStatus na WABA; qualityRating e status no número) e só percorre WABAs próprias. Não detecta recurso criado ou removido, nem divergência em template, app, catálogo ou WABA de clienteParcial
Relatório de status de webhook incompletolevels.phoneNumber volta sempre vazio; overrideCallbackUri volta sempre null e inheritsFromApp é fixo em true quando há assinatura — não refletem o estado realParcial
Sem sincronização agendadaNão há cron. Sync e verificação de token só acontecem por chamada explícitaRoadmap
Sem alerta ativo de expiraçãoO status EXPIRING_SOON só é calculado quando alguém chama token/check. Ninguém checa sozinhoRoadmap — combine com um job externo
appsecret_proof enviado vaziogenerateSystemUserToken envia o parâmetro como string vazia. Funciona enquanto o aplicativo não exigir prova de segredo; se a exigência for ativada na Meta, a chamada passa a falharDívida técnica conhecida
System user não pode ser excluídoLimitação da Meta, não nossa: não há endpoint de exclusão. Criar é definitivoPor design da Meta
Recursos da Meta não cobertosQR codes, modo de coexistência, analytics de WABA, configurações de comércio, analytics de preço, métricas de desempenho de template, upload de documento de verificação, migração de número entre WABAs, atividades da WABARoadmap — priorização em docs/analysis.md

16Perguntas frequentes

A Catalisa vira dona da minha conta de WhatsApp?

Não, e o código não permite. A entrada obrigatória do POST /discover é o identificador do seu Business Manager mais um token que você gera dentro dele. Não existe chamada no BB que crie Business Manager ou WABA em nome da Catalisa, e não estendemos linha de crédito da Meta — o que significa que a Meta fatura você diretamente. Se decidir encerrar a relação, revogue o token no painel da Meta: a conta, o número, os templates e o histórico de qualidade continuam onde sempre estiveram.

Por que isso importa tanto? É só formalidade?

Não é. A Meta documenta que a migração de WABA entre parceiros é iniciada pelo provedor atual, que precisa marcar a conta e desabilitar a verificação em duas etapas do número (Migrating a WABA between Multi-Partner Solutions). Quem detém a conta detém um veto operacional sobre a sua saída, e esse poder aparece na mesa na hora de renovar contrato. Com a conta no seu nome, a troca de fornecedor não passa por autorização de ninguém.

Então o meta-account substitui meu BSP?

Não completamente, e é importante ser claro. Ele não envia mensagem — quem faz isso é o wpp-business — e não oferece linha de crédito da Meta. Se você precisa que alguém banque o consumo da Meta por você, o modelo BSP resolve isso e nós não. O que ele substitui é a dependência do painel de terceiro para saber e mudar o estado da sua própria conta Meta.

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

Um cuida da conta, o outro da conversa. O meta-account descobre e vigia os ativos Meta: WABAs, números, templates, apps, system users, catálogos, webhooks e a saúde do token. O wpp-business usa esses ativos para enviar mensagem, rodar campanha, atender no inbox e processar pedido. Na prática, o meta-account produz os três valores — wabaId, businessId e accessToken — que o wpp-business consome ao criar uma conta. Hoje essa passagem é manual: não há chamada automática entre os dois (§12).

Preciso mesmo criar um system user? Não posso usar o token do painel?

Pode, e vai funcionar por cerca de 60 dias. Depois ele expira e você recebe erro 190 subcódigo 463 — normalmente fora do horário comercial. O token de system user pode ser configurado para não expirar, e é o que o POST /system-users/:id/generate-token gera. Vale os dez minutos de configuração.

Por que GET /wabas/:id devolve 404 com o ID que vejo no painel da Meta?

Porque as rotas usam o UUID interno da linha no banco, não o identificador numérico da Meta. É a confusão mais comum deste BB. Liste os recursos por GET /businesses/:id/graph e use o campo id de cada um; o identificador da Meta aparece ao lado, em metaWabaId, metaPhoneNumberId e assim por diante. Os campos metaAppId e metaWabaId dos corpos de webhook também são UUIDs internos, apesar do nome.

O relatório de divergência voltou vazio. Posso confiar que nada mudou?

Não. Ele compara quatro campos e percorre apenas as WABAs próprias — não detecta recurso criado ou removido, nem mudança em template, app ou catálogo (§15). Para uma reconciliação de verdade, rode POST /businesses/:id/sync e compare os contadores de created, updated e removed.

Quantas mensagens meu número pode disparar por dia?

O BB não responde isso hoje: a coluna messagingLimitTier existe no schema e nunca é preenchida (§15). Consulte o painel da Meta. Vale saber que a regra mudou em 7 de outubro de 2025: os limites passaram a ser calculados por business portfolio, e não mais por número, e os degraus atuais são 250, 2.000, 10.000, 100.000 e ilimitado — o antigo degrau de 1.000 deixou de existir (Upcoming changes to messaging limits).

Excluí uma configuração de webhook e os eventos continuam chegando. É bug?

É comportamento atual, e está documentado na §15. O DELETE /webhooks/:id faz exclusão lógica do nosso registro — ele não cancela a assinatura na Meta. Os métodos para isso existem no cliente HTTP mas não estão expostos em rota. Enquanto isso, cancele pelo painel da Meta e confirme com GET /businesses/:id/webhooks/status.

Quanto custa?

A precificação do building block está em definição, e este documento não estima valor (§6). O custo de mensagem é outra conta e não é nossa: ele vai direto da Meta para você, porque a conta é sua. Desde 1º de julho de 2025 a Meta cobra por mensagem entregue, não mais por conversa, e mensagens de service são gratuitas desde novembro de 2024. Baixe o rate card vigente na documentação da Meta antes de montar qualquer projeção — as tarifas por país mudam trimestralmente em 2026.


Padrão: PADRAO-DOCUMENTACAO.md · Aprofundamento técnico e roadmap: docs/analysis.md

Building blocks relacionados