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.
- 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
- 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
- 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.
| Atributo | Valor |
|---|---|
| Identificador | meta-account |
| Categoria | Comunicação |
| Escopo | Tenant (exige organizationId no token, em todas as rotas) |
| Porta (standalone) | 3028 |
| Path alias | @meta-account |
| Prefixo HTTP | /meta-account |
| Status | Beta desde 2026-04 |
| Depende de | PostgreSQL (schema meta_account), Redis, Meta Graph API v25.0 |
| Permissões | META_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
190com subcódigo463significa "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
| Antes | Depois |
|---|---|
| A conta oficial está no Business Manager do fornecedor | A 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 telas | Um POST /discover mapeia o portfólio inteiro e grava o espelho |
| Webhook em três níveis, configurado a mão, falhando em silêncio | POST /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ério | Catalisa Meta Account | 360dialog | Twilio | Infobip | Take Blip | Cloud API direta |
|---|---|---|---|---|---|---|
| Quem é dono da WABA | Cliente, sempre | Cliente, no BM dele | Cliente | Cliente final | Não declarado publicamente | Cliente |
| Quem fatura o uso da Meta | Meta, ao cliente | Meta, ao cliente (taxas separadas) | Twilio, com taxa por mensagem | Infobip, via linha de crédito própria | Take Blip, plano com franquia | Meta, ao cliente |
| Doc pública de migração de saída | Não se aplica (token revogável) | Posicionamento anti-lock-in publicado | Não localizada | Não localizada | Não localizada | Não se aplica |
| Descoberta automática do portfólio | Sim, um POST | Não | Não | Não | Não | Você implementa |
| Webhooks nos três níveis unificados | Sim (aplicativo e WABA) | Não | Parcial | Não | Não | Você implementa |
| Detecção de divergência | Sim | Não | Não | Não | Não | Você implementa |
| Saúde de token com alerta | Sim, com evento | Não | Não | Não | Não | Você implementa |
| Envio de mensagem e campanha | Não (é o wpp-business) | Sim | Sim | Sim | Sim | Você implementa |
| Linha de crédito da Meta | Não | Não | Não | Sim | Sim | Nã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
- 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. - A descoberta substitui o cadastro. Um
POSTpercorre 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. - 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. - 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.
| Driver | Por quê |
|---|---|
| Business Managers conectados | Cada um é um espelho e um token a vigiar |
| WABAs e números monitorados | Volume de sincronização e de verificação de divergência cresce com eles |
| Frequência de sincronização | Cada 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 template | Cobrança |
|---|---|
| Marketing | Sempre cobrada |
| Utility | Cobrada fora da janela de atendimento; gratuita dentro de janela aberta |
| Authentication | Cobrada fora da janela; sujeita a faixas de volume com taxas menores |
| Service | Gratuita 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 Account | 360dialog | Twilio | Take Blip | |
|---|---|---|---|---|
| Base de cálculo | Precificação em definição | €49 a €249 por número/mês + taxas Meta à parte | US$ 0,005 por mensagem sobre o custo Meta | Plano mensal com franquia de conversas |
| Camada de mensagem inclusa | Não — é o wpp-business | Sim | Sim | Sim |
| Custo Meta | Faturado direto ao cliente | Cobrado à parte, explicitamente | Embutido na fatura Twilio | Embutido no plano |
| Transparência do custo de origem | Total (conta é do cliente) | Alta | Média | Baixa |
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.
BusinessServiceeAppServicepassam todo retorno portoSafeBusiness/toSafeApp, que removem o campo cifrado e o substituem porhasAccessToken/hasAppSecretbooleanos. UmGETno negócio não vaza token nem por acidente de serialização. - Retry só no que adianta repetir. O
MetaGraphClientrepete três vezes, com espera de 1s, 2s e 4s, apenas quando o erro traduzido éINTERNAL,SERVICE_UNAVAILABLEouTIMEOUT. 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 comis_valid: false, não401. 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/:idrecebe o UUID da linha emmeta_wabas, não o identificador numérico da WABA na Meta. Os camposmetaAppIdemetaWabaIdnos 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 usaupsertByMetaIdmais exclusão lógica de quem sumiu do lado da Meta, o que tornadiscoveresyncseguros de repetir. - O nível de webhook por número não existe porque a Meta não o oferece. O enum
MetaWebhookLeveltem o valorPHONE_NUMBER, mas não há endpoint correspondente na Meta: os eventos trafegam pela WABA e são distinguidos pormetadata.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
| Termo | Significa |
|---|---|
| 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. |
| WABA | WhatsApp 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 cliente | A 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 ID | Identificador 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 Secret | Segredo do aplicativo. Combinado ao App ID na forma `{appId} |
| System User | Usuá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ário | O 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 Signup | Fluxo 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ócio | Business 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ção | Display 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úmero | A 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 Prisma | Tabela | Propósito | Campos-chave |
|---|---|---|---|
MetaBusiness | meta_businesses | O Business Manager conectado; raiz do grafo | metaBusinessId, accessToken (cifrado), verificationStatus, lastSyncStatus |
MetaWaba | meta_wabas | Conta de WhatsApp Business | metaWabaId, ownershipType, businessVerificationStatus, accountReviewStatus, currency |
MetaPhoneNumber | meta_phone_numbers | Número dentro de uma WABA | metaPhoneNumberId, displayPhoneNumber, qualityRating, codeVerificationStatus, nameStatus, throughputLevel, isPinEnabled |
MetaApp | meta_apps | Aplicativo Meta do BM | metaAppId, namespace, appSecret (cifrado, opcional) |
MetaSystemUser | meta_system_users | Usuário de sistema do BM | metaSystemUserId, role |
MetaCatalog | meta_catalogs | Catálogo de produtos | metaCatalogId, productCount |
MetaProduct | meta_products | Produto dentro de um catálogo | metaProductId, retailerId, price, currency, availability |
MetaTemplateSnapshot | meta_template_snapshots | Retrato de um template da WABA | metaTemplateId, language, category, status, qualityScore, components |
MetaWebhookConfig | meta_webhook_configs | Configuração de webhook registrada | level, callbackUrl, verifyToken, subscribedFields, overrideCallbackUri |
MetaTokenHealth | meta_token_health | Saúde do token do negócio (1 por negócio) | status, tokenType, scopes, missingScopes, expiresAt, lastCheckedAt |
MetaSyncLog | meta_sync_logs | Histórico de sincronizações | syncType, 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
| Enum | Valores |
|---|---|
MetaSyncStatus | PENDING · IN_PROGRESS · COMPLETED · FAILED |
MetaWebhookLevel | APP · WABA · PHONE_NUMBER (este último declarado, não usado — ver §15) |
MetaTokenStatus | VALID · 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,:catalogIde:productIdé o UUID interno da linha no banco, validado porvalidateUuidParam. Não é o identificador numérico da Meta. Os camposmetaAppIdemetaWabaIdnos corpos de webhook também são UUIDs internos, apesar do nome.
Descoberta — /meta-account/api/v1/discover
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /meta-account/api/v1/discover | Valida o token e mapeia o portfólio Meta inteiro | META_ACCOUNT_MANAGE |
Negócios — /meta-account/api/v1/businesses
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /meta-account/api/v1/businesses | Lista os Business Managers conectados | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:id | Busca um negócio (sem o token) | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:id/graph | Grafo completo: WABAs, números, templates, webhooks, apps, system users, catálogos e saúde do token | META_ACCOUNT_READ |
PATCH | /meta-account/api/v1/businesses/:id | Atualiza nome e/ou rotaciona o access token | META_ACCOUNT_MANAGE |
DELETE | /meta-account/api/v1/businesses/:id | Exclusão lógica do negócio. Responde 204 | META_ACCOUNT_MANAGE |
WABAs — /meta-account/api/v1/wabas
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /meta-account/api/v1/wabas/:id | Busca uma WABA | META_ACCOUNT_READ |
GET | /meta-account/api/v1/wabas/:id/phone-numbers | WABA com os números aninhados | META_ACCOUNT_READ |
GET | /meta-account/api/v1/wabas/:id/templates | WABA com os retratos de template aninhados | META_ACCOUNT_READ |
Números — /meta-account/api/v1/phone-numbers
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /meta-account/api/v1/phone-numbers/:id | Busca um número | META_ACCOUNT_READ |
POST | /meta-account/api/v1/phone-numbers/:id/request-verification | Pede código de verificação por SMS ou VOICE | META_ACCOUNT_MANAGE |
POST | /meta-account/api/v1/phone-numbers/:id/verify | Confirma o código recebido | META_ACCOUNT_MANAGE |
Templates, aplicativos e system users
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /meta-account/api/v1/templates/:id | Busca um retrato de template | META_ACCOUNT_READ |
PATCH | /meta-account/api/v1/apps/:id | Grava o appSecret cifrado do aplicativo | META_ACCOUNT_MANAGE |
POST | /meta-account/api/v1/system-users/:id/generate-token | Gera token de system user (sem expiração) | META_ACCOUNT_MANAGE |
GET | /meta-account/api/v1/businesses/:businessId/system-users | Lista os system users do negócio | META_ACCOUNT_READ |
POST | /meta-account/api/v1/businesses/:businessId/system-users | Cria system user na Meta e grava. Responde 201 | META_ACCOUNT_MANAGE |
Webhooks
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /meta-account/api/v1/webhooks/app | Assina o webhook no nível do aplicativo. Responde 201 | META_ACCOUNT_MANAGE |
POST | /meta-account/api/v1/webhooks/waba | Assina o webhook no nível da WABA. Responde 201 | META_ACCOUNT_MANAGE |
DELETE | /meta-account/api/v1/webhooks/:id | Exclusão lógica da configuração. Responde 204 | META_ACCOUNT_MANAGE |
GET | /meta-account/api/v1/businesses/:businessId/webhooks | Lista as configurações registradas da organização | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:businessId/webhooks/status | Consulta a Meta ao vivo e devolve o estado por nível | META_ACCOUNT_READ |
POST | /meta-account/api/v1/businesses/:businessId/webhooks/auto-configure | Assina todos os apps com segredo e todas as WABAs | META_ACCOUNT_MANAGE |
Catálogos e produtos
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /meta-account/api/v1/businesses/:businessId/catalogs | Lista os catálogos do negócio | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products | Lista os produtos do catálogo | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/:productId | Busca um produto | META_ACCOUNT_READ |
POST | /meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products | Cria produto na Meta e grava. Responde 201 | META_ACCOUNT_MANAGE |
PATCH | /meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/:productId | Atualiza produto na Meta e no espelho | META_ACCOUNT_MANAGE |
DELETE | /meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/:productId | Remove na Meta e exclui logicamente. Responde 204 | META_ACCOUNT_MANAGE |
POST | /meta-account/api/v1/businesses/:businessId/catalogs/:catalogId/products/sync | Reimporta os produtos do catálogo a partir da Meta | META_ACCOUNT_MANAGE |
Sincronização e saúde do token
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /meta-account/api/v1/businesses/:businessId/sync | Sincronização completa | META_ACCOUNT_MANAGE |
POST | /meta-account/api/v1/businesses/:businessId/sync/:resourceType | Sincronização de um tipo só | META_ACCOUNT_MANAGE |
GET | /meta-account/api/v1/businesses/:businessId/sync/history | Histórico de sincronizações | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:businessId/sync/drift | Compara espelho local com a Meta | META_ACCOUNT_READ |
GET | /meta-account/api/v1/businesses/:businessId/token/health | Resumo da saúde do token, com orientação | META_ACCOUNT_READ |
POST | /meta-account/api/v1/businesses/:businessId/token/check | Reconsulta a Meta e regrava a saúde | META_ACCOUNT_MANAGE |
Valores aceitos em :resourceType: wabas, phone_numbers, templates, apps, system_users, catalogs. Qualquer outro devolve 400.
Saúde do serviço
| Método | Rota | Descrição |
|---|---|---|
GET | /meta-account/health | Sonda 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>"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
businessId | string | Sim | O identificador numérico do seu Business Manager na Meta — não um UUID |
accessToken | string | Sim | Token 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
| Status | Quando |
|---|---|
400 | VALIDATION — token inválido segundo a Meta, ou faltando escopo obrigatório. O corpo traz details.missingScopes e details.currentScopes |
401 | Token da Meta expirado ou revogado; a mensagem já vem traduzida com orientação |
403 | Sem META_ACCOUNT_MANAGE, ou token do IAM sem organizationId |
503 | Limite 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"]
}
}
missingScopescompara contra os escopos recomendados, não só os obrigatórios. Um token comstatus: VALIDemissingScopespreenchido está funcional — apenas sem acesso a mensageria ou catálogo.requiredScopesna 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"]
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
metaAppId | string (UUID) | Sim | UUID interno da linha em meta_apps — não o App ID da Meta |
callbackUrl | string (URL) | Sim | A Meta faz verificação real desta URL, com validação de TLS |
verifyToken | string | Sim | Mínimo de 8 caracteres |
objectType | string | Não | Padrão whatsapp_business_account |
fields | string[] | Sim | Ao 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:
- Em
business.facebook.com, abra Configurações do negócio → Usuários → Usuários do sistema. - Crie um system user com papel
ADMIN(ou use um existente). - Atribua a ele os ativos: a WABA, o aplicativo e o catálogo.
- Clique em Gerar novo token, escolha o aplicativo e marque, no mínimo,
business_managementewhatsapp_business_management. Somewhatsapp_business_messagingecatalog_managementse for usar mensageria e catálogo. - 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/463num sábado. - A
callbackUrldo 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-businessespera os identificadores da Meta (wabaId,businessId), não os UUIDs internos do meta-account. Leia-os dos camposmetaWabaIdemetaBusinessId. - 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-configuresó toca aplicativos comappSecretgravado. Um aplicativo sem segredo é silenciosamente ignorado — rodePATCH /apps/:idantes e confira ohasAppSecret.- A operação é parcialmente tolerante a falha por desenho: uma WABA que falha não aborta as outras. Sempre leia o array
results; um200não significa que tudo deu certo. - Ela usa um conjunto fixo de campos:
messages,message_template_status_update,message_template_quality_updateeaccount_update. Se precisar de outros, usePOST /webhooks/appcom 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 block | Como se relaciona | Obrigatório |
|---|---|---|
| IAM | Emite o JWT; o organizationId do token é o tenant, e as permissões META_ACCOUNT_* são verificadas em toda rota | Sim |
| wpp-business | O consumidor natural. Recebe wabaId, businessId e accessToken — os três valores que o meta-account descobre e valida — para criar a conta que envia mensagem | Não, mas é o par esperado |
| wpp | Building block de WhatsApp multi-driver; a camada Meta oficial é provisionada aqui | Não |
| Webhooks Engine | Pode receber e redistribuir os eventos de domínio (meta-account.token.expiring, meta-account.sync.driftDetected) para o cliente | Não |
| Audit Trail | Registra quem rotacionou token, quem alterou webhook, quem removeu negócio | Nã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ável | Descrição | Obrigatória | Padrão |
|---|---|---|---|
META_ACCOUNT_CREDENTIAL_MASTER_KEY | Chave AES-256-GCM que cifra tokens e segredos no banco. 64 caracteres hexadecimais (32 bytes). Gere com openssl rand -hex 32 | Sim, para usar o módulo | — |
MODULE_META_ACCOUNT_PORT | Porta no modo standalone | Não | 3028 |
MODULE_META_ACCOUNT_URL | URL do módulo em standalone; vazio significa local | Não | '' |
META_ACCOUNT_WEBHOOK_BASE_URL | Declarada em src/shared/config/env.ts, mas nenhum código a lê hoje. A URL de webhook vem sempre do corpo da requisição | Não | — |
DATABASE_URL | PostgreSQL | Sim | — |
REDIS_URL | Redis, usado pelo limite de taxa comum | Sim | — |
DEPLOYMENT_MODE | monolith ou standalone | Não | monolith |
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ência | Para quê |
|---|---|
| PostgreSQL | Schema meta_account, 11 tabelas |
| Redis | Contadores do rateLimitMiddleware aplicado por applyCommonMiddleware |
| Meta Graph API v25.0 | https://graph.facebook.com/v25.0 — a única integração externa |
Limites e quotas
| Limite | Valor | Origem |
|---|---|---|
| Corpo da requisição | 1 MB | applyCommonMiddleware |
| Tentativas contra a Meta | 3, com espera de 1s, 2s e 4s | MetaGraphClient |
| Itens por consulta à Meta | 100, fixo, sem paginação de continuação | limit=100 nas 8 consultas de listagem |
| Janela de alerta de expiração | 7 dias | TokenHealthService |
| Limite de taxa da Meta | 200 × usuários ativos diários por hora | Meta; monitore o cabeçalho X-Business-Use-Case-Usage |
| Números por portfólio novo | 2, subindo para 20 com verificação de negócio | Meta |
| Tier inicial de mensagens | 250 destinatários únicos, por business portfolio | Meta, desde 2025-10-07 |
Catálogo de erros
Erros da plataforma:
| Status | Código | Significa | O que fazer |
|---|---|---|---|
400 | VALIDATION | Corpo reprovado no Zod, ou resourceType desconhecido | Confira campos e tipos contra a §9 |
400 | VALIDATION | Token da Meta inválido ou sem escopo obrigatório, em POST /discover | Leia details.missingScopes e gere token novo com os escopos |
400 | VALIDATION | appSecret ausente ao assinar webhook de aplicativo | Rode PATCH /apps/:id antes |
401 | UNAUTHORIZED | JWT do IAM ausente ou expirado | Renove no IAM |
403 | FORBIDDEN | Sem META_ACCOUNT_READ/META_ACCOUNT_MANAGE, ou token sem organizationId | Confira a permissão e o contexto de organização |
404 | NOT_FOUND | UUID inexistente, de outra organização, ou logicamente excluído | Confira se está usando o UUID interno, não o ID da Meta |
500 | INTERNAL | Falha ao decifrar o token — normalmente chave mestra trocada | Verifique META_ACCOUNT_CREDENTIAL_MASTER_KEY |
Erros da Meta, já traduzidos por translateMetaError, com o objeto original preservado em details.meta:
| Código Meta | Subcódigo | Vira | O que fazer |
|---|---|---|---|
190 | 458 | 401 | Aplicativo não instalado — vincule o app ao Business Manager |
190 | 459 | 401 | Checkpoint de segurança — entre na Meta e resolva |
190 | 460 | 401 | Senha alterada desde a emissão — gere token novo |
190 | 463 | 401 | Token expirado. Troque por token de system user |
190 | 467 | 401 | Token inválido ou revogado — gere outro em System Users |
190 | 492 | 401 | Sessão alterada — autentique de novo |
102 | — | 401 | Sessão da API expirada |
10 | — | 403 | Escopo faltando no token |
200–299 | — | 403 | Permissões insuficientes — revise os ativos atribuídos ao system user |
4, 17, 32 | — | 503 | Limite de taxa da Meta — espere alguns minutos |
80000–80099 | — | 503 | Limite de taxa do WhatsApp — reduza a vazão |
100 | — | 400 | Parâmetro inválido; a mensagem original vem junto |
130429 | — | 503 | Limite de taxa da Cloud API para aquele número |
131005 | — | 403 | Número não registrado, ou falta whatsapp_business_messaging |
131016 | — | 503 | Indisponibilidade temporária do WhatsApp |
131048 | — | 503 | Restrição por taxa de spam |
1 / 2 | — | 500 / 503 | Erro 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 prefixometa-account.. As constantestoken.expiring,sync.started,sync.failedesync.driftDetectedestã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/historya expõe. É a primeira coisa a olhar quando alguém diz que "o espelho está errado".MetaTokenHealth.lastCheckedAtdiz há quanto tempo a saúde não é reavaliada. Um valor antigo significa que ninguém está rodandotoken/check— o BB não verifica sozinho.rawMetaguarda a resposta original da Meta em cada recurso, edetails.metaacompanha cada erro traduzido comfbtrace_id. Junto, é o que permite abrir chamado na Meta com evidência.GET /meta-account/healthresponde com o payload padrão de versão. É uma sonda de processo vivo, não verifica banco nem conectividade com a Meta — diferente do/healthdo 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 where — findById(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ção | Impacto | Situação |
|---|---|---|
| Não publicado em staging nem produção | Nã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 hospedado | Beta — não prometa data de disponibilidade a cliente |
| Sem testes unitários e de integração | A 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 credenciais | Lacuna reconhecida |
Funcionalidade no schema, ausente no código
| Limitação | Impacto | Situação |
|---|---|---|
MetaWaba.messagingLimitTier nunca é preenchido | A 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 BB | Não implementado — não anuncie monitoramento de tier |
MetaWebhookLevel.PHONE_NUMBER declarado e não usado | Nenhuma 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ário | Por design; o enum é enganoso |
MetaWebhookConfig.lastVerifiedAt e MetaTokenHealth.errorMessage nunca populados | Sempre nulos | Não implementado |
META_ACCOUNT_WEBHOOK_BASE_URL declarada e não lida | A variável existe em env.ts e nenhum código a consulta; a URL vem sempre do corpo | Não implementado |
| Eventos declarados e nunca publicados | token.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 nada | Não implementado |
Capacidade sem rota HTTP
| Limitação | Impacto | Situação |
|---|---|---|
| Criar e excluir catálogo | CatalogService.create e .delete existem e chamam a Meta, mas nenhuma rota os expõe. Só a listagem está publicada | Implementado no serviço, sem rota |
| Vincular catálogo a WABA | CatalogService.getWabaCatalogs existe, sem rota | Implementado no serviço, sem rota |
| Listagens intermediárias | Nã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 aninhamentos | Parcial |
| Detalhes ao vivo da Meta | getWabaDetails, getPhoneNumberDetails, getCatalogDetails e getProduct existem no cliente HTTP, sem rota. As leituras servem o espelho, não a Meta | Implementado no provider, sem rota |
| Cancelar assinatura de webhook na Meta | deleteAppSubscription e unsubscribeWabaFromApp existem no cliente, mas DELETE /webhooks/:id só faz exclusão lógica local — a assinatura continua ativa na Meta | Divergência relevante; confira no painel da Meta |
Cobertura funcional
| Limitação | Impacto | Situação |
|---|---|---|
| Sem Embedded Signup | O 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 manuais | Roadmap — é a maior lacuna de experiência |
| Sem paginação de continuação | As 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 silenciosamente | Não implementado — limite real para operação grande |
| Detecção de divergência rasa | Compara 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 cliente | Parcial |
| Relatório de status de webhook incompleto | levels.phoneNumber volta sempre vazio; overrideCallbackUri volta sempre null e inheritsFromApp é fixo em true quando há assinatura — não refletem o estado real | Parcial |
| Sem sincronização agendada | Não há cron. Sync e verificação de token só acontecem por chamada explícita | Roadmap |
| Sem alerta ativo de expiração | O status EXPIRING_SOON só é calculado quando alguém chama token/check. Ninguém checa sozinho | Roadmap — combine com um job externo |
appsecret_proof enviado vazio | generateSystemUserToken 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 falhar | Dívida técnica conhecida |
| System user não pode ser excluído | Limitação da Meta, não nossa: não há endpoint de exclusão. Criar é definitivo | Por design da Meta |
| Recursos da Meta não cobertos | QR 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 WABA | Roadmap — 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