Você integra uma vez e passa a abrir contas, movimentar Pix e enviar TED. O banco parceiro que carrega a licença fica atrás de uma configuração — trocar de provedor deixa de ser um projeto e vira uma linha no banco de dados.
- Fintechs de crédito que precisam de conta escrow e liquidação por Pix sem virar instituição autorizada
- Marketplaces e plataformas B2B que recebem em nome de terceiros e precisam de conciliação por conta
- Empresas que já operam com um provedor bancário e querem reduzir o risco de depender de um só
- Integração direta e artesanal com a API de cada banco parceiro
- Camada caseira de tradução entre o formato do provedor e o modelo da sua aplicação
- Tabela própria de contas, transações e chaves Pix reimplementada dentro de cada produto
- Uma licença bancária — a Catalisa não é instituição autorizada pelo BACEN (ver §14)
- Um ledger contábil ou sistema de conciliação financeira da sua operação
- Um adquirente, gateway de cartão ou emissor de cartão
- Um sistema de antifraude, PLD/FT ou monitoramento transacional
01Resumo executivo
O BaaS é a camada que dá à sua aplicação uma conta bancária de verdade sem que você precise virar um banco. Você chama uma API para abrir conta, consultar saldo, registrar chave Pix, enviar Pix e TED, gerar QR Code e receber os eventos de volta. Quem executa a operação bancária é um provedor licenciado pelo Banco Central — Celcoin, QI Tech ou FitBank — e quem esconde as diferenças entre eles é este building block.
Na prática isso significa que uma financeira consegue abrir uma conta escrow por operação de crédito, liquidar o desembolso por Pix e conciliar o recebimento pelo endToEndId, tudo com o mesmo contrato de API. E significa também que trocar de banco parceiro seis meses depois é criar uma configuração nova e apontar as contas seguintes para ela — não é um projeto de reintegração.
Está deployado nas stacks de staging e produção desde janeiro de 2026, com a esteira completa exercitada de ponta a ponta contra o provedor simulado MockBank. O status é beta, e não produção, de propósito: as três integrações com provedores reais estão implementadas e testadas com HTTP mockado, mas este repositório não guarda evidência de homologação contra os ambientes de sandbox reais desses provedores. §15 detalha exatamente o que falta.
| Atributo | Valor |
|---|---|
| Identificador | baas |
| Categoria | Financeiro |
| Escopo | Tenant (exige organizationId no token) |
| Porta (standalone) | 3015 |
| Path alias | @baas |
| Prefixo HTTP | /baas |
| Schema no banco | baas |
| Status | Beta — deployado desde 2026-01 |
| Depende de | PostgreSQL, Redis, IAM, Webhooks Engine |
02O problemanegócio
O cenário. Uma empresa quer movimentar dinheiro dentro do próprio produto: abrir conta para o cliente, receber por Pix, pagar fornecedor por TED, manter saldo separado por operação. Ela não quer — e na maioria dos casos não pode — pedir autorização ao Banco Central para isso. Então contrata um provedor bancário licenciado e integra a API dele.
O que trava hoje.
- Cada provedor inventa o próprio dialeto. A Celcoin fala REST com endpoints por recurso e status em texto. O FitBank fala RPC: um único endpoint
/main/execute, um campoMethodno corpo e status como número de 0 a 6. A QI Tech assina cada requisição com RSA. São três modelos mentais diferentes para a mesma operação de "manda um Pix". - O provedor vira parte da sua arquitetura. Quando o mapeamento entre o formato do banco e o seu domínio está espalhado pelos serviços, o provedor deixa de ser fornecedor e vira dependência estrutural. A conversa comercial de renovação acontece com você sem alternativa.
- Trocar de banco é reescrever. A migração não é só de API: é de identificador de conta, de formato de status, de semântica de webhook. Times estimam meses e adiam a decisão, que é exatamente o que o incumbente precisa.
- Não dá para testar sem contrato. Sem sandbox liberado, o time não consegue nem começar. A esteira de produto fica bloqueada por um processo comercial e de compliance que leva semanas.
- A conciliação é problema seu, mas o risco também. Quando os saldos que você mostra ao cliente e os que o banco parceiro registra divergem, quem responde ao cliente é você.
O custo de não resolver. O último item não é hipótese. Em 2024 a Synapse Financial Technologies — que operava exatamente como camada entre fintechs e bancos parceiros nos EUA — entrou em Chapter 11, e a curadoria nomeada pelo tribunal apurou uma diferença de US$ 65 a 96 milhões entre os registros da Synapse e os dos bancos parceiros. Clientes finais ficaram sem acesso ao próprio dinheiro; só na Yotta, 13.725 correntistas receberam US$ 11,8 milhões de volta contra US$ 64,9 milhões depositados (Wikipedia — Synapse Financial Technologies). A lição prática: numa arquitetura BaaS, saber quem carrega a licença e onde o registro é a fonte de verdade não é detalhe jurídico, é desenho de sistema.
03Proposta de valornegócio
| Antes | Depois |
|---|---|
| Uma integração artesanal por provedor bancário | Uma API canônica; o provedor é configuração da organização |
| Trocar de banco parceiro é projeto de meses | Criar uma configuração nova e apontar as contas seguintes para ela |
| Só dá para começar depois do contrato com o banco | MockBank simula conta, saldo, DICT, Pix e QR Code desde o primeiro dia |
| Cada provedor exige um formato de webhook diferente | Todos os webhooks viram o mesmo conjunto de 18 eventos internos |
| Credenciais de banco em variável de ambiente | Credenciais cifradas em AES-256-GCM no banco, por organização |
O provedor é dado, não código. BaaSProviderConfig guarda o tipo do provedor e as credenciais cifradas. createBaaSProvider instancia a estratégia certa em tempo de execução. Nenhum serviço do BaaS sabe o nome de um banco.
Várias configurações ativas ao mesmo tempo. Uma organização pode ter Celcoin e QI Tech simultaneamente, ou duas configurações Celcoin com credenciais distintas. Cada BaaSAccount guarda o configId a que pertence, então a migração é por conta, não por empresa.
A esteira inteira é testável sem contrato. O MockBank não é um stub de resposta: é um banco simulado com saldo real, transferência interna entre contas, geração de chave EVP, diretório de chaves, QR Code em formato EMV do BR Code e disparo de webhook. O que você prova no MockBank é o mesmo fluxo que vai rodar em produção.
Credenciais tratadas como segredo. As credenciais do provedor são cifradas com AES-256-GCM antes de tocar o banco e nunca voltam em nenhuma resposta de API — toResponseData simplesmente não as inclui.
04Casos de uso reaisnegócio
Caso 1 — Uma financeira separa o dinheiro de cada operação de crédito Cenário ilustrativo
Contexto. Financeira de crédito com garantia que desembolsa para o cliente e recebe parcelas de volta. Cada operação precisa de segregação de recursos, porque o dinheiro em trânsito não é dela.
A dor. Com uma conta única no banco parceiro, a separação virava planilha. Descobrir quanto do saldo pertencia a qual operação exigia cruzar extrato com sistema, e o processo era o mesmo tanto para fechar o mês quanto para responder a uma dúvida de cliente. O erro não aparecia no dia; aparecia na conciliação.
A solução com o BB. Cada operação abre uma conta pelo POST /baas/api/v1/accounts, com businessId carregando o identificador da operação no sistema de crédito e personId apontando para a pessoa no Customers. O desembolso sai por POST /baas/api/v1/pix/transfers. O recebimento entra como transação PIX_IN criada pelo handler de webhook, já vinculada à conta certa. GET /baas/api/v1/pix/transfers?filter[accountId]=... devolve o extrato daquela operação.
O resultado. A pergunta "quanto desta operação já voltou" deixa de ser um cruzamento manual e vira uma chamada de API filtrada por conta. E como businessId está na transação, o vínculo com o sistema de crédito sobrevive à conciliação.
Caso 2 — Um marketplace cobra por QR Code e reconhece o pagamento sozinho Cenário ilustrativo
Contexto. Marketplace B2B que fatura pedidos de lojistas e quer receber por Pix sem obrigar o comprador a copiar dados bancários.
A dor. O fluxo anterior era boleto com dois dias de compensação e uma rotina de baixa que rodava de madrugada. O comprador pagava, o pedido ficava travado até o dia seguinte, e o atendimento passava a manhã respondendo "já paguei".
A solução com o BB. No fechamento do pedido, o marketplace chama POST /baas/api/v1/pix/qr-codes/generate com amount, description e expiresInMinutes, e recebe o emvCode (copia e cola) mais o QR em base64. Quando o comprador paga, o provedor manda o webhook pix.received para POST /baas/api/v1/webhooks/{provedor}; o handler cria a transação PIX_IN e publica baas.pix.received no barramento de eventos. O Webhooks Engine entrega esse evento ao endpoint do marketplace.
O resultado. O pedido é liberado no momento do pagamento, não no dia seguinte. E a baixa deixa de depender de uma rotina noturna — é o evento que dispara a mudança de estado.
Caso 3 — Uma operação troca de banco parceiro sem parar o produto Cenário ilustrativo
Contexto. Empresa que já opera com um provedor bancário há um ano e decide diversificar, seja por custo, por SLA ou por não querer depender de um fornecedor só.
A dor. Na integração direta, isso é uma virada de chave: reescrever o cliente HTTP, remapear status, refazer o parsing de webhook, migrar todas as contas de uma vez e rezar. O risco concentrado num fim de semana é justamente o motivo pelo qual a decisão é adiada por anos.
A solução com o BB. Cria-se uma segunda BaaSProviderConfig com o novo provedor, valida com POST /baas/api/v1/provider-configs/:id/test, e marca como padrão com POST /baas/api/v1/provider-configs/:id/set-default. As contas novas nascem no provedor novo; as antigas continuam no antigo, porque cada BaaSAccount guarda o próprio configId e o serviço resolve o provedor a partir dele a cada operação. Os dois convivem pelo tempo que for preciso.
O resultado. A migração vira uma curva, não um degrau. E o poder de barganha na renovação muda de lado, porque a alternativa deixa de ser teórica.
Caso 4 — O que acontece quando a camada BaaS não tem dono claro do registro Referência de mercado
Contexto. A Synapse Financial Technologies operava nos EUA como intermediária entre fintechs e bancos parceiros, mantendo o registro de quem era dono de qual saldo dentro das contas dos bancos. Cerca de 100 relações comerciais diretas, com dezenas de aplicativos de consumo em cima (Wikipedia — Synapse Financial Technologies).
A dor do mercado. Em abril de 2024 a empresa entrou em Chapter 11. Quando a curadoria comparou os registros da Synapse com os dos bancos parceiros, apurou um descasamento de US$ 65 a 96 milhões. Correntistas finais — que acreditavam ter dinheiro segurado pelo FDIC — ficaram sem acesso aos próprios recursos por meses. Os clientes da Yotta receberam US$ 11,8 milhões contra US$ 64,9 milhões depositados.
Como a Catalisa endereça. Três escolhas de desenho, todas verificáveis no código. Primeiro: a Catalisa nunca é depositária. O dinheiro fica no provedor licenciado; BaaSAccount guarda externalId, que é o identificador da conta no provedor, e toda operação de saldo passa por provider.getBalance(). Segundo: o saldo local é cache explícito, não fonte de verdade — availableBalance e blockedBalance são atualizados a partir da resposta do provedor, e a consulta de saldo sempre vai ao provedor antes de responder. Terceiro: o endToEndId do Pix é persistido e indexado, o que dá uma chave de conciliação comum entre o seu registro e o do banco.
O resultado. A pergunta "de quem é este saldo" tem uma resposta única e ela mora no banco licenciado. O BaaS é uma camada de tradução com histórico auditável, não um livro-razão paralelo — e essa distinção é a diferença entre os dois desenhos.
05Mercado e diferenciaisnegócio
Panorama. O mercado brasileiro de BaaS tem duas camadas que costumam ser confundidas na conversa comercial. Na primeira estão as instituições autorizadas pelo Banco Central — Celcoin (IP e SCD), QI Tech (SCD), Dock (IP e participante direto do Pix, código 301), Fits/FitBank (IP). São elas que carregam a licença, respondem ao regulador e custodiam recurso. Na segunda estão as camadas técnicas que integram uma ou mais dessas instituições e entregam uma API ao produto final. A Catalisa está, sem ambiguidade, na segunda camada.
Nenhum dos provedores brasileiros consultados publica tabela de preço; todos direcionam para o comercial (consulta em 2026-08-16 aos sites de Celcoin, QI Tech, Dock e Fits). Isso significa que a comparação de custo entre eles só existe dentro de uma negociação — o que, por si só, é um argumento a favor de ter mais de um contrato possível.
No exterior o padrão é o mesmo: a Unit declara em rodapé que "is a financial technology company and not a bank", com os serviços bancários prestados por bancos parceiros membros do FDIC. A diferença é que o mercado americano viveu, com a Synapse, o custo de essa distinção não estar clara para o cliente final.
| Critério | Catalisa BaaS | Celcoin | QI Tech | Dock | Unit (EUA) |
|---|---|---|---|---|---|
| Carrega licença do BACEN | Não — camada técnica | Sim (IP + SCD) | Sim (SCD) | Sim (IP, part. direto Pix) | Não (bancos parceiros, EUA) |
| Custodia recurso de cliente | Não | Sim | Sim | Sim | Não |
| Provedor trocável sem reescrever | Sim, por configuração | Não se aplica | Não se aplica | Não se aplica | Não |
| Mais de um provedor simultâneo | Sim, por organização | Não | Não | Não | Não |
| Ambiente de teste sem contrato | Sim (MockBank completo) | Sandbox após contrato | Sandbox após contrato | Sandbox após contrato | Sandbox |
| Pix, TED e DICT | Sim, via provedor | Sim | Sim | Sim | Não (mercado EUA) |
| Emissão de cartão | Não | Sim | Sim | Sim | Sim |
| Crédito / originação | Não (é o Decision Platform) | Parcial | Sim | Parcial | Sim |
| Boleto | Não implementado (§15) | Sim | Sim | Sim | Não |
| Preço público | Precificação em definição | Não divulgado | Não divulgado | Não divulgado | Não divulgado |
Nossos diferenciais
- O provedor é uma linha no banco de dados.
BaaSProviderConfig.providerTypemais credenciais cifradas é tudo que separa uma organização rodando sobre Celcoin de outra rodando sobre QI Tech. É difícil de copiar não pela técnica, mas porque exige manter três mapeadores completos em dia com três APIs que evoluem sozinhas — custo que só se paga quando ele é diluído por vários clientes. - Coexistência de provedores, não apenas suporte a vários. A distinção importa: suportar N provedores é escolher um por cliente. Aqui
BaaSAccount.configIdamarra cada conta à sua configuração, então duas contas da mesma organização podem viver em bancos diferentes ao mesmo tempo. É o que torna a migração gradual possível. - Um banco simulado de verdade, não um mock de resposta. O MockBank tem saldo, débito e crédito entre contas internas, DICT com chave EVP em formato UUID,
endToEndIdno formato de 32 caracteres da especificação, QR Code EMV parseável e disparo de webhook. O time integra e testa a esteira toda antes de o jurídico terminar o contrato. - Isolamento por organização em toda consulta de escrita. Cada serviço compara
organizationIddo token com o do registro antes de agir, e devolve404— não403— quando não bate, para não revelar existência de recurso de outro tenant.
Quando escolher o concorrente. Se você precisa de uma coisa só — uma conta, um Pix, e nada mais — integrar direto na Celcoin ou na QI Tech é mais simples e tem menos peças no caminho; toda abstração cobra um pedágio e o menor denominador comum vale aqui também (o ADR-0003 registra isso como consequência aceita). Se o produto central é emissão de cartão, a Dock resolve algo que o BaaS não faz e não pretende fazer. Se a exigência é crédito com originação, custódia e fundo no mesmo contrato, a QI Tech entrega uma esteira que aqui exigiria combinar vários building blocks. E se você precisa de boleto hoje, isso não está implementado (§15). O BaaS ganha quando o problema é não querer que o banco parceiro vire dependência estrutural — e quando a operação é multi-tenant, porque aí a configuração por organização deixa de ser conveniência e vira arquitetura.
06Modelo de cobrança e ROInegócio
Unidade de cobrança. Precificação em definição. Não há preço fechado para este building block, e inventar um aqui seria pior do que não ter.
O que dispara custo. Três drivers, e vale entender a natureza de cada um:
| Driver | Por quê |
|---|---|
| Contas ativas | O provedor cobra por conta aberta e mantida; é o custo que cresce com a base |
| Transações Pix e TED processadas | Custo por transação do provedor, repassado |
| Chamadas de API ao provedor | Consulta de saldo, DICT e status batem no provedor a cada requisição (§7 explica por que não há cache) |
Comparação de custo. Não é possível montar tabela comparativa honesta: nenhum dos provedores consultados publica preço (verificado em 2026-08-16 nos sites de Celcoin, QI Tech, Dock e Fits). Toda comparação de custo entre eles acontece dentro de uma proposta comercial específica. O que dá para comparar é a estrutura de custo:
| Integração direta com um provedor | Catalisa BaaS | |
|---|---|---|
| Custo transacional | Do provedor, negociado | Do provedor, negociado — o mesmo |
| Custo de integração inicial | Uma integração completa por provedor | Uma integração; o provedor entra por configuração |
| Custo de trocar de provedor | Nova integração completa | Nova configuração; contas antigas continuam no provedor antigo |
| Custo de ter dois provedores | Duas integrações completas e mantidas | Duas configurações |
| Custo de testar antes do contrato | Bloqueado até liberação de sandbox | Zero — MockBank |
ROI. O retorno não está na taxa por Pix: essa é do banco parceiro e continua sendo, com ou sem BaaS. Está em duas coisas. A primeira é o custo de integração que não se repete: manter dois mapeadores de provedor em produção é trabalho contínuo, e aqui ele é diluído entre todos os clientes da plataforma em vez de recair sobre a engenharia de cada um. A segunda é menos visível e vale mais: um provedor que sabe que você não tem alternativa técnica negocia diferente de um que sabe que você tem. A conta dessa diferença não aparece na linha de engenharia — aparece na taxa por transação, todo mês.
07Arquitetura
HTTP
│
┌─────────────────────┴──────────────────────────────────────────────┐
│ Hono app basePath('/baas') + applyCommonMiddleware │
│ bodyLimit 1MB · CORS · security headers · rate limit │
│ │
│ /api/v1/provider-configs providerConfigRouter (7 rotas) │
│ /api/v1/accounts accountRouter (9 rotas) │
│ /api/v1/pix pixRouter (10 rotas) │
│ /api/v1/ted tedRouter (3 rotas) │
│ /api/v1/webhooks webhookRouter (2 rotas) │
│ /health versão do build │
│ │
│ authMiddleware → requirePermission → requireOrganization │
│ (webhookRouter é a exceção: não passa por JWT — ver §14) │
└─────────────────────┬──────────────────────────────────────────────┘
│ Zod parse → ResultAsync<T, AppError>
┌─────────────────────┴──────────────────────────────────────────────┐
│ services/ │
│ ProviderConfigService CRUD de config, cifra credencial, testa │
│ AccountService conta, saldo, KYC, encerramento │
│ PixService chave, DICT, transferência, QR, estorno │
│ TedService transferência TED e consulta │
│ WebhookHandlerService recebe, deduplica, aplica e republica │
└─────────────────────┬──────────────────────────────────────────────┘
│ decryptCredentials + createBaaSProvider(tipo)
┌─────────────────────┴──────────────────────────────────────────────┐
│ providers/ interface BaaSProvider — 24 métodos canônicos │
│ │
│ CelcoinProvider OAuth2 client_credentials · REST · status texto │
│ FitBankProvider Basic Auth · RPC /main/execute · status numérico│
│ QITechProvider requisição assinada com RSA · REST │
│ MockBankProvider banco simulado, tabelas mockbank_* no Postgres │
└─────────────────────┬──────────────────────────────────────────────┘
│
┌─────────────────────┴──────────────────────────────────────────────┐
│ repositories/ (Prisma) → PostgreSQL, schema "baas" │
└─────────────────────────────────────────────────────────────────────┘
Caminho de uma transferência Pix:
cliente ──▶ pixRouter ──▶ PixService ──▶ providerConfigRepo (busca config
│ da conta) ──▶ decryptCredentials
│
├──▶ provider.initiatePixTransfer(...) ← rede
│
└──▶ transactionRepo.create(...) ← só depois
│
└──▶ eventPublisher.publish(
'baas.pix.transfer.initiated')
Decisões não óbvias.
- O provedor é instanciado por requisição, não guardado no container.
getProviderForAccountbusca a config, decifra as credenciais e chamacreateBaaSProvidera cada operação. O custo é uma decifragem AES por chamada — irrelevante perto da latência de rede do provedor. O ganho é que trocar credencial ou desativar uma config surte efeito na requisição seguinte, sem reinício e sem invalidação de cache. Num building block financeiro, revogar credencial na hora vale mais que economizar microssegundos. - Saldo não tem cache.
GET /accounts/:id/balancesempre bate no provedor. O campoavailableBalanceno banco é atualizado depois, de forma assíncrona, e existe para listagem — nunca para responder "quanto tem nesta conta". Um saldo desatualizado de 30 segundos numa tela é irritação; num sistema que decide se autoriza uma transferência, é prejuízo. - A transação local só é criada depois que o provedor confirma. A ordem é sempre provedor primeiro, banco depois. Se o provedor falhar, não existe transação órfã em
PENDINGno seu banco esperando conciliação manual. O trade-off aceito é o inverso: se o provedor aceitar e a escrita local falhar, existe uma transação no banco parceiro sem contrapartida local — que é o cenário que a reconciliação porendToEndIdprecisa cobrir (§15). - Tipos canônicos, e não o formato do provedor, atravessam o serviço.
CanonicalAccount,CanonicalPixTransaction,CanonicalBalancee companhia são o contrato interno. Nenhum serviço sabe que o FitBank usaTaxNumbercomo identificador de conta ou que a Celcoin devolve status em texto. A consequência é o menor denominador comum: uma funcionalidade exclusiva de um provedor não é exposta enquanto não couber na interface. O ADR-0003 registra isso como custo aceito. externalIdé o identificador no provedor, e é ele que amarra tudo. A unicidade é(configId, externalId), nãoexternalIdsozinho — porque dois provedores diferentes podem, legitimamente, emitir o mesmo identificador. É a mesma razão pela qual não existe conta "da organização": existe conta "da configuração", e a configuração é que é da organização.- Webhook do provedor não passa por JWT. Quem chama é o banco, que não tem token da sua plataforma. A autenticação é assinatura do provedor, e a organização vem do header
X-Organization-Idou do query paramorganizationIdque o próprio BaaS gera na URL de callback. §14 detalha o modelo de confiança e o que ligar em produção. - O MockBank persiste em tabelas próprias.
mockbank_accounts,mockbank_pix_keys,mockbank_transactionsemockbank_qr_codessão o "banco de dados do banco simulado", separadas das tabelasbaas_*que são o registro da plataforma. A separação é deliberada: o simulador tem que se comportar como um sistema externo, inclusive perdendo e reconciliando estado, senão o teste não prova nada.
Monolito vs. standalone. Em monolito, registerBaas monta tudo no container TypeDI e o MockBank recebe o PrismaClient e o callback de webhook, que simula o POST do provedor chamando o WebhookHandlerService direto. Em standalone — o modo de produção — o BaaS sobe na porta 3015 com MODULE_SELF=baas, e a única dependência de rede é o IAM, via MODULE_IAM_URL, para validação de token. A verificação da assinatura JWT é local em ambos os modos.
08Conceitos e modelo de dados
Glossário
| Termo | Significa |
|---|---|
| Provider config | Configuração de um provedor bancário para uma organização: tipo, credenciais cifradas e ajustes. Uma organização pode ter várias, e uma delas é a padrão. |
| Provider | Implementação da interface BaaSProvider para um banco específico. Instanciada por requisição a partir da config. |
| Tipo canônico | Formato interno neutro (CanonicalAccount, CanonicalPixTransaction, ...) para o qual todo provedor traduz. É o contrato que isola o serviço do banco. |
externalId | Identificador do recurso no provedor. Único por (configId, externalId). É a chave de correlação com o mundo externo. |
endToEndId | Identificador único da transação Pix definido pela especificação do BACEN, com 32 caracteres. É a chave de conciliação entre o seu registro e o do banco. |
| DICT | Diretório de Identificadores de Contas Transacionais — o cadastro de chaves Pix do Banco Central. POST /pix/keys/lookup consulta quem é dono de uma chave. |
| Chave EVP | Chave Pix aleatória, em formato UUID. É a única que o BaaS gera sem você informar valor. |
| EMV / BR Code | Formato do payload de QR Code Pix. O campo emvCode é o "copia e cola". |
| KYC | Verificação de identidade do titular. O BaaS encaminha documentos ao provedor e reflete o status; a análise é do provedor. |
| MockBank | Provedor simulado, com estado próprio no Postgres. Para desenvolvimento e teste — nunca para produção. |
Modelo de dados — schema baas no PostgreSQL. Cinco modelos de domínio e quatro do simulador.
| Modelo Prisma | Tabela | Propósito | Campos-chave |
|---|---|---|---|
BaaSProviderConfig | baas.baas_provider_configs | Provedor configurado para a organização | providerType, credentials (cifrado), isDefault, isActive, settings, único (organizationId, name) |
BaaSAccount | baas.baas_accounts | Conta bancária mantida no provedor | externalId, configId, status, bankCode, branchNumber, accountNumber, availableBalance, personId, businessId, único (configId, externalId) |
BaaSPixKey | baas.baas_pix_keys | Chave Pix registrada para uma conta | keyType, keyValue, status, deletedAt, único (accountId, keyType, keyValue) |
BaaSTransaction | baas.baas_transactions | Transação Pix ou TED, entrada ou saída | transactionType, status, amount Decimal(15,2), endToEndId, isCredit, originalTransactionId, único (accountId, externalId) |
BaaSWebhookEvent | baas.baas_webhook_events | Evento cru recebido do provedor | eventType, externalEventId, payload, processed, error, único (configId, externalEventId) |
MockBankAccount | baas.mockbank_accounts | Estado interno do banco simulado | externalId, saldo, dados do titular, kycStatus |
MockBankPixKey | baas.mockbank_pix_keys | DICT do banco simulado | keyValue único globalmente |
MockBankTransaction | baas.mockbank_transactions | Extrato do banco simulado | endToEndId, type, status |
MockBankQrCode | baas.mockbank_qr_codes | QR Codes emitidos pelo banco simulado | emvCode, expiresAt |
O
único (configId, externalEventId)embaas_webhook_eventsé o que dá idempotência ao recebimento de webhook: o mesmo evento reentregue pelo provedor é detectado e descartado antes de ser aplicado.
Enumerações
| Enum | Valores |
|---|---|
BaaSProviderType | CELCOIN · FITBANK · QITECH · MOCKBANK |
BaaSAccountStatus | PENDING_KYC · KYC_IN_PROGRESS · KYC_REJECTED · ACTIVE · BLOCKED · CLOSED |
BaaSTransactionType | PIX_IN · PIX_OUT · TED_IN · TED_OUT · REVERSAL |
BaaSTransactionStatus | PENDING · PROCESSING · COMPLETED · FAILED · CANCELLED · REVERSED |
BaaSPixKeyType | CPF · CNPJ · EMAIL · PHONE · EVP |
BaaSPixKeyStatus | PENDING · ACTIVE · INACTIVE |
KycStatus | NOT_STARTED · PENDING · IN_PROGRESS · APPROVED · REJECTED · REQUIRES_UPDATE |
Ciclo de vida da conta
POST /accounts
│
▼
┌─────────────┐ KYC enviado ┌──────────────────┐ aprovado ┌────────┐
│ PENDING_KYC │ ──────────────▶ │ KYC_IN_PROGRESS │ ─────────▶ │ ACTIVE │
└─────────────┘ └────────┬─────────┘ └───┬────┘
▲ │ reprovado │
│ webhook kyc.requires_update ▼ │
│ ┌──────────────┐ │
└───────────────────────── │ KYC_REJECTED │ │
└──────────────┘ │
│
webhook account.blocked ┌─────────┐ │
┌───────────────────────────▶ │ BLOCKED │ ◀───────────────────┤
│ └─────────┘ │
│ │
│ POST /accounts/:id/close ou webhook account.closed │
│ ┌────────┐ │
└───────────────────────────▶ │ CLOSED │ ◀─────────────────────┘
└────────┘
terminal
Quem move o estado: o provedor. O BaaS reflete, via resposta de
submitKyc ou via webhook. Encerrar é a única transição que a sua
aplicação inicia — e ela também passa pelo provedor antes de gravar.
CLOSED é terminal: fechar de novo devolve 400.
Máquina de estados da transação — vale para Pix e TED, saída e entrada.
POST /pix/transfers · POST /ted
│
│ provider.initiate*Transfer()
│ (o status inicial é o que o provedor devolveu)
▼
┌───────────────────────┐
│ PENDING │
└───────────┬───────────┘
│ webhook *.transfer.created
▼
┌───────────────────────┐
│ PROCESSING │
└───────┬───────┬───────┘
webhook *.completed │ │ webhook *.failed
▼ ▼
┌───────────────┐ ┌────────┐ ┌───────────┐
│ COMPLETED │ │ FAILED │ │ CANCELLED │
└───────┬───────┘ └────────┘ └───────────┘
│ terminal terminal, sem
│ transição no código
POST /transfers/:id/refund
(só de PIX_IN e só se COMPLETED)
│
▼
┌───────────────┐ e cria uma transação nova
│ REVERSED │ ─────▶ REVERSAL, ligada pelo campo
└───────────────┘ originalTransactionId
terminal
Entrada (PIX_IN, TED_IN) nasce direto em COMPLETED: ela é criada pelo
handler ao receber o webhook pix.received / ted.received, ou seja, o
dinheiro já entrou quando o registro aparece.
GET /pix/transfers/:id e GET /ted/:id NÃO são consultas passivas —
eles chamam o provedor e gravam o status novo se ele mudou. É o
caminho de reconciliação para quando um webhook se perde.
Eventos publicados no barramento — 18 eventos, consumíveis pelo Webhooks Engine com o filtro baas.*.
| Grupo | Eventos |
|---|---|
| Conta | baas.account.created · baas.account.updated · baas.account.closed · baas.account.blocked |
| KYC | baas.kyc.submitted · baas.kyc.approved · baas.kyc.rejected |
| Pix | baas.pix.key.created · baas.pix.key.deleted · baas.pix.transfer.initiated · baas.pix.transfer.completed · baas.pix.transfer.failed · baas.pix.received · baas.pix.refunded |
| TED | baas.ted.transfer.initiated · baas.ted.transfer.completed · baas.ted.transfer.failed · baas.ted.received |
09Referência da API
Prefixo: /baas. Em standalone, a base é https://baas.bb.stg.catalisa.app.
Todas as rotas de negócio — as 29 fora de /webhooks — exigem, nesta ordem: authMiddleware (Bearer JWT válido), requirePermission(...) e requireOrganization (o token precisa carregar organizationId, senão 403). As permissões são apenas duas: BAAS_READ para leitura e BAAS_ADMIN para qualquer escrita.
Prefixo duplicado. Todo router está montado duas vezes: em
/baas/api/v1/{recurso}(canônico) e em/baas/api/v1/baas/{recurso}(compatibilidade com clientes e testes antigos). Use o canônico em código novo; o segundo existe e funciona, mas não é o caminho preferencial.
Configurações de provedor — /baas/api/v1/provider-configs
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /baas/api/v1/provider-configs | Cria config, testa a conexão e cifra as credenciais. 201 | BAAS_ADMIN |
GET | /baas/api/v1/provider-configs | Lista, paginado. Filtros filter[providerType], filter[isActive] | BAAS_READ |
GET | /baas/api/v1/provider-configs/:id | Busca por ID | BAAS_READ |
PATCH | /baas/api/v1/provider-configs/:id | Atualiza. Se enviar credentials, testa antes de gravar | BAAS_ADMIN |
DELETE | /baas/api/v1/provider-configs/:id | Exclusão lógica. 204 | BAAS_ADMIN |
POST | /baas/api/v1/provider-configs/:id/set-default | Marca como padrão e desmarca as outras | BAAS_ADMIN |
POST | /baas/api/v1/provider-configs/:id/test | Testa a conexão com as credenciais gravadas | BAAS_ADMIN |
Contas — /baas/api/v1/accounts
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /baas/api/v1/accounts | Abre conta no provedor e registra localmente. 201 | BAAS_ADMIN |
GET | /baas/api/v1/accounts | Lista, paginado. Filtros filter[status], filter[personId], filter[configId] | BAAS_READ |
GET | /baas/api/v1/accounts/:id | Busca por ID | BAAS_READ |
PATCH | /baas/api/v1/accounts/:id | Atualiza titular no provedor e/ou metadata local | BAAS_ADMIN |
POST | /baas/api/v1/accounts/:id/close | Encerra a conta. Exige reason | BAAS_ADMIN |
GET | /baas/api/v1/accounts/:id/balance | Saldo consultado no provedor a cada chamada | BAAS_READ |
POST | /baas/api/v1/accounts/:id/kyc | Envia documentos de KYC | BAAS_ADMIN |
GET | /baas/api/v1/accounts/:id/kyc | Consulta o status de KYC no provedor | BAAS_READ |
POST | /baas/api/v1/accounts/:id/sandbox/deposit | Só MockBank. Credita saldo para teste | BAAS_ADMIN |
Pix — /baas/api/v1/pix
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /baas/api/v1/pix/keys | Registra chave Pix. 201 | BAAS_ADMIN |
GET | /baas/api/v1/pix/keys | Lista chaves. Exige ?accountId=, senão 400 | BAAS_READ |
DELETE | /baas/api/v1/pix/keys/:id | Remove a chave no provedor e marca deletedAt. 204 | BAAS_ADMIN |
POST | /baas/api/v1/pix/keys/lookup | Consulta o DICT: quem é dono de uma chave | BAAS_READ |
GET | /baas/api/v1/pix/transfers | Lista transações Pix da organização, com dados da conta | BAAS_READ |
POST | /baas/api/v1/pix/transfers | Inicia transferência Pix. 201 | BAAS_ADMIN |
GET | /baas/api/v1/pix/transfers/:id | Consulta e sincroniza o status com o provedor | BAAS_READ |
POST | /baas/api/v1/pix/transfers/:id/refund | Devolve um Pix recebido. 201 | BAAS_ADMIN |
POST | /baas/api/v1/pix/qr-codes/generate | Gera QR Code estático ou dinâmico | BAAS_ADMIN |
POST | /baas/api/v1/pix/qr-codes/decode | Decodifica um código EMV | BAAS_READ |
Filtros de GET /pix/transfers: page[number], page[size], filter[accountId], filter[configId], filter[status], filter[direction] (in ou out), filter[startDate], filter[endDate].
TED — /baas/api/v1/ted
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /baas/api/v1/ted | Inicia transferência TED. 201 | BAAS_ADMIN |
GET | /baas/api/v1/ted | Lista transações TED. Aceita ?accountId= ou os mesmos filtros do Pix | BAAS_READ |
GET | /baas/api/v1/ted/:id | Consulta e sincroniza o status com o provedor | BAAS_READ |
GET /tedmuda de comportamento conforme o filtro: comaccountIde semconfigId, devolve a listagem por conta; caso contrário, devolve a listagem global com os dados da conta embutidos em cada item.
Webhooks — /baas/api/v1/webhooks
| Método | Rota | Descrição | Autenticação |
|---|---|---|---|
POST | /baas/api/v1/webhooks/:providerType | Recebe webhook do provedor. :providerType é celcoin, fitbank, qitech ou mockbank | Assinatura do provedor — não usa JWT |
GET | /baas/api/v1/webhooks/health | Sonda simples do subsistema de webhook | Pública |
Saúde
| Método | Rota | Descrição |
|---|---|---|
GET | /baas/health | Identificação e versão do build. Não valida banco nem provedor |
POST /baas/api/v1/provider-configs
Cria a configuração de um provedor. Antes de gravar, o serviço chama testConnection() no provedor — credencial errada falha aqui, não na primeira conta.
Todos os endpoints de escrita aceitam o corpo em dois formatos: direto, ou dentro de data.attributes. Os dois são equivalentes.
Request
{
"name": "Celcoin Produção",
"providerType": "CELCOIN",
"credentials": {
"clientId": "SEU_CLIENT_ID",
"clientSecret": "SEU_CLIENT_SECRET",
"sandbox": "false"
},
"isDefault": true,
"isActive": true,
"clientWebhookUrl": "https://sua-api.exemplo.com.br/webhooks/baas",
"clientWebhookEvents": ["baas.pix.received", "baas.pix.transfer.completed"]
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
name | string (1–100) | Sim | Único dentro da organização. Colisão devolve 409 |
providerType | CELCOIN | FITBANK | QITECH | MOCKBANK | Sim | Determina qual estratégia é instanciada |
credentials | Record<string,string> | Sim | Formato depende do provedor — ver §13. Cifrado antes de gravar |
isDefault | boolean | Não | Marcar como padrão desmarca a anterior |
isActive | boolean | Não | Padrão true. Config inativa não pode virar padrão |
settings | object | Não | Ajustes específicos do provedor |
webhookUrl | string (URL) | Não | Onde o provedor entrega. Omitido, o serviço gera a partir de BAAS_WEBHOOK_BASE_URL |
webhookEvents | string[] | Não | Filtra o que é processado. Vazio significa todos |
clientWebhookUrl | string (URL) | Não | Onde você quer receber. Cria uma assinatura no Webhooks Engine |
clientWebhookEvents | string[] | Não | Filtros de evento baas.*. Vazio vira baas.* |
Resposta 201
{
"data": {
"type": "baas-provider-config",
"id": "6f1c2e40-8d9a-4b1e-9c3f-1a2b3c4d5e6f",
"links": { "self": "/api/v1/baas/provider-configs/6f1c2e40-..." },
"attributes": {
"name": "Celcoin Produção",
"providerType": "CELCOIN",
"isDefault": true,
"isActive": true,
"webhookUrl": "https://baas.bb.stg.catalisa.app/api/v1/baas/webhooks/celcoin?organizationId=...",
"createdAt": "2026-08-16T12:00:00.000Z"
}
}
}
As credenciais não voltam nesta resposta, nem em nenhuma outra. Não há endpoint que as devolva.
Erros
| Status | Quando |
|---|---|
400 | Corpo reprovado no Zod, credenciais incompletas, ou testConnection() falhou |
403 | Token sem organizationId, ou sem BAAS_ADMIN |
409 | Já existe config com esse name na organização |
POST /baas/api/v1/accounts
Abre a conta no provedor e registra localmente. Se configId for omitido, usa a configuração padrão da organização.
Request
{
"configId": "6f1c2e40-8d9a-4b1e-9c3f-1a2b3c4d5e6f",
"personId": "9b7e1d22-5a10-4f88-b3d1-77c9e0a41b52",
"businessId": "OP-2026-00841",
"holder": {
"name": "Maria Oliveira",
"documentType": "CPF",
"documentNumber": "00000000191",
"email": "maria@exemplo.com.br",
"phone": "+5511999990000",
"birthDate": "1990-05-15",
"motherName": "Ana Oliveira",
"address": {
"street": "Rua Exemplo",
"number": "100",
"neighborhood": "Centro",
"city": "São Paulo",
"state": "SP",
"zipCode": "01234567",
"country": "BR"
}
}
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
configId | uuid | Não | Config a usar. Omitido, usa a padrão |
personId | uuid | Não | Vínculo com Person do Customers. Não é chave estrangeira — ver §15 |
businessId | string | Não | Seu identificador de negócio. Fica na conta e nas transações |
holder.documentType | CPF | CNPJ | Sim | Define quais campos o provedor vai exigir |
holder.documentNumber | string (11–14) | Sim | Sem formatação |
holder.legalRepresentative | object | Não | Para CNPJ: representante legal |
metadata | object | Não | Chaves livres, guardadas em JSONB |
Resposta 201
{
"data": {
"type": "baas-account",
"id": "3c9a77e1-2b45-4c8d-9e10-5f6a7b8c9d01",
"attributes": {
"status": "PENDING_KYC",
"bankCode": "999",
"branchNumber": "0001",
"accountNumber": "0000042",
"accountDigit": "7",
"availableBalance": 0,
"blockedBalance": 0,
"personId": "9b7e1d22-5a10-4f88-b3d1-77c9e0a41b52",
"businessId": "OP-2026-00841",
"createdAt": "2026-08-16T12:00:00.000Z"
}
}
}
O status inicial é o que o provedor devolveu. Com MockBank e autoApproveKyc ligado, a conta já nasce ACTIVE.
POST /baas/api/v1/pix/transfers
Inicia uma transferência Pix. Informe pixKey ou recipient — sem um dos dois, 400.
Request — por chave Pix
{
"accountId": "3c9a77e1-2b45-4c8d-9e10-5f6a7b8c9d01",
"amount": 150.75,
"description": "Pagamento pedido 8841",
"pixKey": "destinatario@exemplo.com.br"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
accountId | uuid | Sim | Conta de origem. Precisa ser da sua organização |
amount | number > 0 | Sim | Em reais, com centavos. Precisão de Decimal(15,2) |
description | string (≤140) | Não | Vai no campo livre do Pix |
pixKey | string | Condicional | Chave do destinatário. Celcoin e FitBank consultam o DICT antes de enviar |
recipient | object | Condicional | Dados bancários completos, quando não há chave |
scheduledFor | string (ISO 8601) | Não | Agendamento. Suporte depende do provedor |
businessId | string | Não | Seu identificador, guardado na transação |
idempotencyKey | uuid | Não | Aceito pelo schema, mas hoje não repassado ao provedor — ver §15 |
Resposta 201 — data.attributes traz transactionType: "PIX_OUT", status, amount, fee, endToEndId, os campos counterparty* e processedAt.
Erros
| Status | Quando |
|---|---|
400 | Sem pixKey nem recipient; saldo insuficiente; valor acima do limite; conta bloqueada ou encerrada |
403 | Token sem organizationId ou sem BAAS_ADMIN |
404 | accountId inexistente ou de outra organização — a distinção é deliberada (§14) |
POST /baas/api/v1/pix/transfers/:id/refund
Devolve um Pix recebido. Duas regras verificadas antes de chamar o provedor: a transação precisa ser PIX_IN e precisa estar COMPLETED. Qualquer outra combinação devolve 400.
{ "reason": "Cobrança duplicada", "amount": 50.00 }
amount é opcional; omitido, devolve o valor integral. A resposta 201 traz a transação REVERSAL nova, e a original passa a REVERSED.
POST /baas/api/v1/webhooks/:providerType
Endpoint que o provedor chama. Não passa por JWT.
A organização vem do header X-Organization-Id ou do query param organizationId — sem um dos dois, 400. A URL que o BaaS gera automaticamente já inclui o query param.
Pipeline de processamento, nesta ordem:
- Valida que
:providerTypeé conhecido. - Localiza a config ativa daquele tipo, na organização.
verifyWebhookSignature()— comportamento controlado porPROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT(§13).parseWebhookPayload()traduz o corpo do provedor paraParsedBaaSWebhook.- Filtra por
webhookEventsda config; evento não assinado é descartado com{ received: true }. - Deduplica por
(configId, externalEventId); repetido é descartado com{ received: true }. - Grava em
baas_webhook_events, aplica o efeito e publica o eventobaas.*.
Resposta 200 — { "received": true }.
Falha ao aplicar o efeito não vira erro HTTP: o evento é gravado com o erro no campo error e a resposta continua 200. É deliberado — provedor que recebe erro reentrega, e reentrega de um evento já persistido não conserta nada.
10Início rápido
Do zero à primeira transferência Pix, usando MockBank. Não precisa de contrato com banco nenhum. Todos os comandos abaixo seguem o fluxo exercitado pelos testes end-to-end em tests/e2e/api/baas.e2e.test.ts.
1. Autenticar no IAM
BASE=https://baas.bb.stg.catalisa.app/baas
ORG=b0000000-0000-0000-0000-000000000001
TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
-H "Content-Type: application/json" \
-d "{\"email\":\"admin@catalisa.app\",\"password\":\"root123456\",\"organizationId\":\"$ORG\"}" \
| jq -r .accessToken)
echo "${TOKEN:0:24}..."
2. Criar a configuração do MockBank
CONFIG=$(curl -s -X POST "$BASE/api/v1/provider-configs" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "MockBank Sandbox",
"providerType": "MOCKBANK",
"credentials": { "token": "mockbank-dev", "autoApproveKyc": "true", "simulateDelays": "false" },
"isDefault": true,
"isActive": true
}')
CONFIG_ID=$(echo "$CONFIG" | jq -r '.data.id')
echo "$CONFIG_ID"
O POST já testou a conexão. Se voltou 201, o provedor respondeu.
3. Abrir uma conta
ACC=$(curl -s -X POST "$BASE/api/v1/accounts" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{
\"configId\": \"$CONFIG_ID\",
\"holder\": {
\"name\": \"Maria Oliveira\",
\"documentType\": \"CPF\",
\"documentNumber\": \"00000000191\",
\"email\": \"maria@exemplo.com.br\",
\"birthDate\": \"1990-05-15\"
}
}")
ACC_ID=$(echo "$ACC" | jq -r '.data.id')
echo "$ACC" | jq '.data.attributes | {status, bankCode, accountNumber, accountDigit}'
{ "status": "ACTIVE", "bankCode": "999", "accountNumber": "0000001", "accountDigit": "3" }
Banco 999 é o código fictício do MockBank. Nenhum banco real usa esse número — é assim que você sabe, olhando um extrato, que está em ambiente simulado.
4. Registrar uma chave Pix aleatória
curl -s -X POST "$BASE/api/v1/pix/keys" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"accountId\":\"$ACC_ID\",\"keyType\":\"EVP\"}" \
| jq '.data.attributes | {keyType, keyValue, status}'
EVP é a única que dispensa keyValue — o provedor gera um UUID. Para CPF, CNPJ, EMAIL ou PHONE, informar keyValue é obrigatório.
5. Depositar saldo (só MockBank)
curl -s -X POST "$BASE/api/v1/accounts/$ACC_ID/sandbox/deposit" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"amount": 1000}' | jq
{ "availableBalance": 1000, "blockedBalance": 0, "totalBalance": 1000, "currency": "BRL" }
Este endpoint existe só no MockBank. Nos provedores reais ele devolve 400 — em banco de verdade, saldo entra por Pix ou TED recebido.
6. Enviar um Pix
curl -s -X POST "$BASE/api/v1/pix/transfers" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{
\"accountId\": \"$ACC_ID\",
\"amount\": 150.75,
\"description\": \"Primeiro Pix\",
\"recipient\": {
\"name\": \"Destinatário Teste\",
\"documentNumber\": \"00000000272\",
\"bankCode\": \"001\",
\"branchNumber\": \"0001\",
\"accountNumber\": \"12345\",
\"accountType\": \"CHECKING\"
}
}" | jq '.data.attributes | {status, amount, endToEndId}'
{ "status": "COMPLETED", "amount": 150.75, "endToEndId": "E9999999920260816120012AB34CD56EF78" }
7. Conferir o saldo
curl -s "$BASE/api/v1/accounts/$ACC_ID/balance" -H "Authorization: Bearer $TOKEN" | jq
Volta 849.25. Este número veio do provedor, não da coluna do banco — foi ele quem debitou.
Credenciais de staging, documentadas em AMBIENTES.md. Nunca use credencial de provedor real, chave de produção ou dado de conta de cliente em documentação, script de exemplo ou issue.
11Receitas
Migrar de provedor sem parar a operação
Objetivo. Passar as contas novas para outro banco parceiro mantendo as antigas onde estão.
# 1. Criar a config nova. testConnection roda aqui — se a credencial
# estiver errada, você descobre agora e não no primeiro cliente.
NEW=$(curl -s -X POST "$BASE/api/v1/provider-configs" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"QI Tech Produção","providerType":"QITECH",
"credentials":{"apiKey":"...","privateKey":"...","sandbox":"false"},
"isActive":true}')
NEW_ID=$(echo "$NEW" | jq -r '.data.id')
# 2. Validar de novo, explicitamente
curl -s -X POST "$BASE/api/v1/provider-configs/$NEW_ID/test" \
-H "Authorization: Bearer $TOKEN" | jq
# 3. Virar o padrão. A partir daqui, POST /accounts sem configId
# abre no provedor novo.
curl -s -X POST "$BASE/api/v1/provider-configs/$NEW_ID/set-default" \
-H "Authorization: Bearer $TOKEN" | jq '.data.attributes.isDefault'
# 4. Acompanhar a distribuição por provedor
curl -s "$BASE/api/v1/accounts?filter[configId]=$OLD_ID&page[size]=1" \
-H "Authorization: Bearer $TOKEN" | jq '.meta.totalItems'
Armadilhas.
- Não desative nem apague a config antiga enquanto houver conta nela. Cada operação resolve o provedor a partir do
configIdda conta; sem a config, a conta antiga fica inacessível — inclusive para consultar saldo. - A config nova precisa da própria URL de webhook cadastrada no painel do provedor. O BaaS gera a URL, mas quem a informa ao banco é você. Sem isso, os eventos de entrada param de chegar para as contas novas e você só descobre quando alguém reclama que um Pix recebido não apareceu.
- Contas não migram. Não existe endpoint que mova uma conta de um provedor para outro, e não existiria: a conta é do banco. Migrar de verdade significa encerrar lá e abrir cá, com o titular no meio.
set-defaultafeta só o que vier depois. Nenhuma conta existente muda de provedor.
Reconciliar uma transferência quando o webhook não chegou
Objetivo. Descobrir o estado real de uma transação que ficou parada em PENDING ou PROCESSING.
# GET não é passivo: ele consulta o provedor e grava o status novo
curl -s "$BASE/api/v1/pix/transfers/$TX_ID" -H "Authorization: Bearer $TOKEN" \
| jq '.data.attributes | {status, processedAt, failureReason, endToEndId}'
# Varrer tudo o que está pendente há mais tempo que o esperado
curl -s "$BASE/api/v1/pix/transfers?filter[status]=PROCESSING&filter[direction]=out&page[size]=100" \
-H "Authorization: Bearer $TOKEN" | jq -r '.data[] | "\(.id) \(.attributes.createdAt)"'
Armadilhas.
- Não existe job de reconciliação automática. A varredura acima é sua para agendar. Um webhook perdido deixa a transação parada indefinidamente até alguém consultar (§15).
GETbate no provedor a cada chamada. Varrer centenas de transações em laço apertado consome quota da API do banco. Espace as chamadas.endToEndIdé a chave de conciliação com o extrato do banco, não oidda transação nem oexternalId. É o único identificador que os dois lados reconhecem.
Receber por QR Code e reagir ao pagamento
Objetivo. Cobrar por Pix e ser notificado quando o dinheiro entrar.
# 1. Assinar os eventos de entrada, na criação ou atualização da config
curl -s -X PATCH "$BASE/api/v1/provider-configs/$CONFIG_ID" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"clientWebhookUrl":"https://sua-api.exemplo.com.br/hooks/baas",
"clientWebhookEvents":["baas.pix.received"]}' | jq '.data.attributes.webhookSubscriptionId'
# 2. Gerar o QR dinâmico
curl -s -X POST "$BASE/api/v1/pix/qr-codes/generate" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"accountId\":\"$ACC_ID\",\"amount\":89.90,
\"description\":\"Pedido 8841\",\"expiresInMinutes\":30}" \
| jq '{emvCode, expiresAt}'
Quando o pagamento entra, o provedor chama o webhook, o handler cria a transação PIX_IN e publica baas.pix.received. O Webhooks Engine entrega no seu endpoint.
Armadilhas.
expiresInMinutestem teto de 1440 — 24 horas. Acima disso o Zod reprova com400.- Sem
amount, o QR é estático: reutilizável e sem valor definido. Comamount, é dinâmico e de uso único. Gerar estático para cobrança de pedido faz o mesmo código ser pago duas vezes. - A criação da assinatura de webhook falha em silêncio. Se o Webhooks Engine estiver indisponível, o
PATCHregistra umwarnno log e devolve200mesmo assim, comwebhookSubscriptionIdvazio. Confira o campo na resposta — não assuma que assinou.
Configurar o KYC de uma conta pessoa física
Objetivo. Enviar documentos e acompanhar a análise.
curl -s -X POST "$BASE/api/v1/accounts/$ACC_ID/kyc" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"identityDocument": { "type": "CNH", "front": "https://storage.../frente.jpg" },
"selfie": "https://storage.../selfie.jpg",
"proofOfAddress": "https://storage.../comprovante.pdf"
}' | jq
curl -s "$BASE/api/v1/accounts/$ACC_ID/kyc" -H "Authorization: Bearer $TOKEN" \
| jq '{status, rejectionReason, rejectedFields}'
Armadilhas.
- O corpo da requisição é limitado a 1 MB por
bodyLimit. Enviar documento em base64 estoura esse limite com facilidade. Prefira subir o arquivo pelo File Storage e mandar a URL — os campos aceitam base64 ou URL. - O status da conta muda junto:
APPROVEDleva a conta aACTIVE,REJECTEDaKYC_REJECTED, e qualquer outra coisa aKYC_IN_PROGRESS. - Quem decide é o provedor. O BaaS não avalia documento; ele encaminha e reflete.
rejectedFieldsdiz o que reenviar. - Conta sem KYC aprovado não transaciona. No MockBank isso é explícito (
KYC_NOT_APPROVEDvira403); nos provedores reais, a recusa vem deles.
Diagnosticar por que uma chamada voltou 400
O BaaS devolve 400 com dois formatos diferentes, e saber qual é encurta o diagnóstico:
# Falha de schema Zod, direto no router: { "error": { <árvore de campos> } }
curl -s -X POST "$BASE/api/v1/pix/transfers" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"accountId":"nao-e-uuid","amount":-5}' | jq
# Falha de regra de negócio, vinda do serviço: { "error": "VALIDATION", "message": "..." }
curl -s -X POST "$BASE/api/v1/pix/transfers" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"accountId\":\"$ACC_ID\",\"amount\":1}" | jq
Ordem de diagnóstico: o corpo passa no schema? A conta é da sua organização (senão vem 404, não 403)? A config está ativa? Tem saldo? A conta está ACTIVE? O valor está abaixo do limite do provedor?
12Integração com outros building blocks
| Building block | Como se relaciona | Obrigatório |
|---|---|---|
| IAM | Emite o token; organizationId, BAAS_READ e BAAS_ADMIN vêm dele | Sim |
| Webhooks Engine | ProviderConfigService cria, atualiza e apaga a assinatura de saída quando você define clientWebhookUrl | Sim — é dependência de container |
| Customers | BaaSAccount.personId aponta para Person; filter[personId] lista as contas de uma pessoa | Não |
| Audit Trail | Consome os eventos baas.* para trilha de compliance | Não |
| File Storage | Guarda os documentos de KYC; o BaaS recebe a URL, não o arquivo | Não |
| Payments | Cobrança e conciliação de recebíveis; o BaaS é a conta onde o dinheiro cai | Não |
| Open Finance | Agrega dados de contas em outras instituições; o BaaS opera a conta que é sua | Não |
| Decision Platform | Decide se concede o crédito; o BaaS desembolsa o resultado | Não |
┌─────────┐
│ IAM │ emite o token com organizationId + permissões
└────┬────┘
│ Bearer JWT
▼
┌───────────────┐ ┌────────────────────────┐ API do provedor
│ Decision │ │ │ ─────────────────────▶ ┌──────────┐
│ Platform │─▶│ BaaS │ │ Celcoin │
│ (aprovou?) │ │ │ ◀───────────────────── │ QI Tech │
└───────────────┘ │ contas · Pix · TED │ webhook │ FitBank │
└───┬───────────┬────────┘ └──────────┘
│ │ licença do BACEN
personId ──────────┘ │ eventos baas.* mora aqui
▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Customers │ │ Webhooks Engine │ ─────▶ │ endpoint do │
│ (quem é) │ │ (entrega) │ │ cliente │
└───────────────┘ └────────┬────────┘ └──────────────────┘
│
▼
┌──────────────┐
│ Audit Trail │
└──────────────┘
O encaixe é o argumento: o Decision Platform decide, o BaaS movimenta, o Customers diz de quem é, o Webhooks Engine avisa e o Audit Trail registra. Nenhuma dessas conexões exige código de integração seu — elas acontecem por evento, com o mesmo organizationId atravessando toda a cadeia.
13Configuração e operação
Variáveis de ambiente
| Variável | Descrição | Obrigatória | Padrão |
|---|---|---|---|
BAAS_CREDENTIAL_MASTER_KEY | Chave AES-256-GCM das credenciais de provedor. Exatamente 64 caracteres hexadecimais (32 bytes). Gere com openssl rand -hex 32 | Sim, para usar o módulo | — |
BAAS_WEBHOOK_BASE_URL | Base das URLs de callback geradas automaticamente | Não | http://localhost:3000 |
PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT | off ou on. Com on, webhook que não puder ser verificado é rejeitado | Não | off |
DATABASE_URL | PostgreSQL | Sim | — |
REDIS_URL | Redis, usado no rate limit | Sim | — |
JWT_SECRET | Verificação do token do IAM. Mínimo 44 caracteres | Sim | — |
MODULE_IAM_URL | URL do IAM em standalone | Em standalone | — |
DEPLOYMENT_MODE | monolith ou standalone | Não | monolith |
PORT | Porta em standalone | Não | 3000 (mapeada para 3015) |
BAAS_CREDENTIAL_MASTER_KEYnão tem rotação automatizada. Trocá-la torna todas as credenciais gravadas indecifráveis — decifre e recifre antes de trocar. Guarde com SOPS, nunca em.envversionado.
Credenciais por provedor — o que vai dentro do campo credentials.
| Provedor | Campos | Autenticação | Ambientes |
|---|---|---|---|
CELCOIN | clientId, clientSecret, sandbox | OAuth2 client_credentials, token em cache até 5 min antes de expirar | sandbox.openfinance.celcoin.dev · api.celcoin.com.br |
FITBANK | apiKey, apiSecret, partnerId, businessUnitId, sandbox | Basic Auth | sandboxapi.fitbank.com.br/main/execute · api.fitbank.com.br/main/execute |
QITECH | apiKey, privateKey, sandbox | Requisição assinada com RSA | sandbox.qitech.app · api-auth.qitech.app |
MOCKBANK | token, autoApproveKyc, simulateDelays, delayMs | Só confere que token não está vazio | Nenhum — roda local |
settings do MockBank aceita defaultBalance, maxPixAmount (padrão 100.000) e maxTedAmount (padrão 500.000), úteis para exercitar o caminho de limite excedido.
Dependências de infraestrutura
| Dependência | Para quê |
|---|---|
| PostgreSQL | Schema baas — configs, contas, chaves, transações, eventos e o estado do MockBank |
| Redis | Contadores de rate limit |
| IAM | Validação do token e resolução de permissões |
| Webhooks Engine | Assinaturas de entrega dos eventos baas.* |
| API do provedor | Toda operação bancária. Indisponibilidade do banco é indisponibilidade da operação |
Limites
| Limite | Valor | Onde |
|---|---|---|
| Corpo da requisição | 1 MB | applyCommonMiddleware — afeta KYC em base64 |
| Rate limit global | 10.000 req / 60s | RATE_LIMIT_GLOBAL_MAX |
| Descrição de Pix e TED | 140 caracteres | Zod |
| Expiração de QR dinâmico | 1 a 1440 minutos | Zod |
| Página padrão / listagens | 20 itens | Routers |
| Precisão monetária | Decimal(15,2) | Prisma |
| Limites de valor por transação | Do provedor | MockBank: 100.000 (Pix) e 500.000 (TED) |
Catálogo de erros
| Status | Código | Significa | O que fazer |
|---|---|---|---|
400 | (árvore Zod) | Corpo reprovado no schema | Compare os campos com §9 |
400 | VALIDATION | Regra de negócio: sem saldo, sem pixKey nem recipient, conta já encerrada, estorno de transação que não é PIX_IN COMPLETED | A message diz qual regra |
400 | VALIDATION | testConnection() falhou ao criar ou atualizar config | Confira as credenciais e o ambiente (sandbox) |
400 | — | Webhook sem X-Organization-Id nem organizationId | Corrija a URL cadastrada no painel do provedor |
401 | UNAUTHORIZED | Token ausente, inválido ou expirado | Renove no IAM |
403 | — | Token sem organizationId | Autentique informando a organização |
403 | FORBIDDEN | Sem BAAS_READ ou BAAS_ADMIN; ou conta bloqueada, encerrada, ou sem KYC aprovado | Confira permissões e o status da conta |
404 | NOT_FOUND | Recurso inexistente ou de outra organização | Confira o ID e a organização do token |
409 | CONFLICT | Nome de config já usado na organização | Escolha outro nome |
429 | — | Rate limit | Recuo exponencial |
500 | INTERNAL | Erro não mapeado do provedor | Consulte o log; o erro cru do provedor fica lá |
Observabilidade.
GET /baas/healthdevolve identificação e versão do build. Não valida banco nem provedor — para orquestrador serve como sonda de vida, não de prontidão.POST /provider-configs/:id/testé a sonda real: exercita credencial e conectividade com o banco parceiro. Vale agendar por config ativa.- Toda tentativa de webhook com assinatura não verificada gera
warnestruturado com o provedor e o motivo, tanto com enforcement ligado quanto desligado. É o log que diz se você pode ligarPROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT=onsem quebrar integração. baas_webhook_eventsé a trilha de auditoria de entrada: guarda o payload cru, se foi processado e o erro em caso de falha.processed = falseacumulando é sintoma.- Erros de comunicação com o provedor são logados com contexto antes de virarem
AppError.
14Segurança e compliance
Quem carrega a licença — leia isto antes de tudo. A Catalisa não é instituição autorizada a funcionar pelo Banco Central do Brasil. Não é banco, não é Instituição de Pagamento, não é SCD nem SEP, não é participante do Pix e não custodia recurso de terceiro. O BaaS é uma camada de software que integra a API de instituições que são autorizadas — Celcoin (IP e SCD), QI Tech (SCD) e FitBank/Fits (IP).
A consequência prática, e ela é contratual antes de ser técnica:
- A conta bancária é do provedor.
BaaSAccount.externalIdé o identificador dela lá. O que existe aqui é um espelho com histórico. - O saldo é do provedor.
GET /accounts/:id/balancesempre consulta a origem. As colunas locais são cache para listagem. - O contrato de conta é entre o titular e a instituição autorizada, com a sua empresa e a Catalisa na cadeia técnica.
- Obrigações regulatórias de PLD/FT, KYC e reporte são da instituição autorizada e, conforme o arranjo, da sua empresa. O BaaS encaminha documento de KYC e reflete o status; ele não avalia, não pontua e não decide.
O enquadramento aplicável ao provedor — não à Catalisa — inclui a Resolução BCB nº 80/2021, que trata da autorização e do funcionamento de instituições de pagamento, e o Regulamento do Pix, Resolução BCB nº 1/2020, que define quem participa do arranjo. Antes de assinar contrato, confirme na lista de instituições autorizadas do BACEN que o seu provedor está lá, e sob qual modalidade. Esta documentação não substitui essa verificação nem parecer jurídico.
Isolamento entre tenants. O organizationId vem do claim assinado do JWT e nunca do corpo da requisição. Todo router de negócio aplica requireOrganization, que devolve 403 quando o claim falta. Em cima disso, cada serviço compara a organização do token com a do registro antes de agir — AccountService.getById, PixService.getAccount, TedService, ProviderConfigService.getById — e devolve 404, não 403, quando não bate. A escolha é deliberada: 403 confirmaria que o recurso existe em outro tenant.
Consultas de listagem recebem organizationId como filtro obrigatório no repositório, não como parâmetro opcional.
Credenciais de provedor. Cifradas com AES-256-GCM por encryptCredentials, no formato iv:authTag:ciphertext, com IV de 12 bytes aleatório por operação. A chave mestra vem de BAAS_CREDENTIAL_MASTER_KEY e é validada como exatamente 64 caracteres hexadecimais — chave malformada faz a operação falhar em vez de degradar para algo mais fraco. O GCM garante autenticidade: ciphertext adulterado falha na decifragem, não decifra errado. As credenciais nunca aparecem em resposta de API — toResponseData não as inclui — e não há endpoint de leitura delas.
Webhooks de entrada. É a única superfície sem JWT, porque quem chama é o banco. O modelo de confiança tem três camadas:
- Assinatura do provedor, verificada por
verifyWebhookSignature()de cada provedor. PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT, que decide o que fazer quando a verificação não é possível:offaceita e registrawarn;onrejeita. O padrão éoff, e para operação com dinheiro real a recomendação éon, depois de conferir nos logs que nenhuma integração legítima está caindo na trilha dowarn. §15 lista quais provedores hoje conseguem verificar de fato.- Restrição de rede. Para provedor sem esquema de assinatura publicado, a proteção viável é allowlist de IP na borda. Trate isso como requisito de implantação, não como opcional.
Somam-se duas propriedades independentes de assinatura: deduplicação por (configId, externalEventId), que impede reprocessar o mesmo evento, e persistência do payload cru em baas_webhook_events, que dá trilha completa do que chegou.
Dados pessoais e LGPD. O BaaS guarda nome, CPF ou CNPJ, e-mail, telefone, endereço, nome da mãe e data de nascimento do titular, além de nome e documento das contrapartes de cada transação. Tudo é dado pessoal; parte é dado financeiro. Três observações operacionais:
- Não há mascaramento de documento nas respostas de API. Quem tem
BAAS_READvê o CPF do titular. TrateBAAS_READcomo permissão sensível, não como leitura genérica. - Documentos de KYC não são guardados aqui. Eles vão para o provedor; o BaaS não persiste imagem nem base64. Se você usa o File Storage como origem das URLs, a retenção é lá.
- Contas e transações não têm exclusão lógica — não há
deletedAtembaas_accountsnem embaas_transactions. É coerente com retenção financeira, e significa que atender a pedido de eliminação exige processo explícito, hoje não automatizado (§15).
Permissões. Só duas, e a granularidade é grossa de propósito: BAAS_READ para tudo que lê, BAAS_ADMIN para tudo que escreve. Quem pode configurar um provedor pode também enviar Pix, porque as duas coisas exigem BAAS_ADMIN. Se a sua operação precisa separar quem configura de quem transaciona, isso hoje se faz com organizações distintas — não com papéis distintos (§15).
Superfície HTTP. Todo o app passa por applyCommonMiddleware: limite de 1 MB no corpo, CORS que bloqueia cross-origin quando não há origens configuradas, cabeçalhos de segurança e rate limit global.
15Limitações conhecidas
Maturidade dos provedores. É a limitação mais importante e a razão do status beta.
| Provedor | Situação real, verificada no código | O que falta |
|---|---|---|
| MockBank | Implementação completa e exercitada de ponta a ponta pelos testes de integração e end-to-end. Persiste em tabelas próprias, faz débito e crédito, gera EVP, endToEndId, QR EMV e dispara webhook | Nada — mas é simulador. Nunca use em produção. Banco 999 e ISPB 99999999 são fictícios |
| Celcoin | Os 24 métodos da interface implementados contra a API REST real, com OAuth2 e cache de token. Testado com HTTP mockado | Verificação de assinatura de webhook não implementada (há TODO no código). Sem evidência de homologação contra o sandbox real neste repositório |
| QI Tech | Os 24 métodos implementados, com requisição assinada por RSA. Testado com HTTP mockado | Verificação de assinatura de webhook não implementada. Sem evidência de homologação contra o sandbox real neste repositório |
| FitBank / Fits | Os 24 métodos implementados sobre a API RPC. Testado com HTTP mockado | O provedor não publica esquema de assinatura de webhook — a proteção viável é allowlist de IP. Sem evidência de homologação contra o sandbox real |
Consequência direta: com PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT=on, os webhooks de Celcoin, QI Tech e FitBank passam a ser rejeitados, porque nenhum dos três consegue verificar de fato. Com off — o padrão — eles são aceitos e um warn é registrado. Não há terceira opção hoje. Antes de operar com dinheiro real, resolva isto: implemente a verificação para Celcoin e QI Tech, e restrinja por IP o FitBank.
Funcionalidades ausentes ou parciais
| Limitação | Impacto | Situação |
|---|---|---|
idempotencyKey aceito mas ignorado | Os schemas de POST /pix/transfers e POST /ted aceitam idempotencyKey, e os quatro provedores sabem repassá-lo. Mas PixService e TedService não o passam adiante. Um retry de rede pode gerar transferência duplicada | Lacuna funcional conhecida. Enquanto não for corrigida, faça controle de duplicidade do seu lado antes de chamar |
| Sem reconciliação automática | Webhook perdido deixa a transação parada em PENDING ou PROCESSING indefinidamente. Não há job de varredura | Faça o polling você — receita em §11 |
| Boleto não implementado | Não há emissão nem baixa de boleto, embora os provedores ofereçam | Fora do escopo atual |
| Bloquear e desbloquear conta não tem rota HTTP | blockAccount e unblockAccount existem na interface e nos quatro provedores, mas nenhum router os expõe. O bloqueio só chega por webhook do provedor | Implementado na camada de provedor, não exposto |
personId não é chave estrangeira | O vínculo com Person do Customers é um UUID solto: sem validação na criação, sem integridade referencial, sem cascata | Por design de desacoplamento entre building blocks — mas o personId inválido só aparece quando alguém tenta usar |
| Agendamento sem garantia uniforme | scheduledFor é aceito e repassado, mas o comportamento é do provedor. O MockBank ignora e executa na hora | Confirme com o seu provedor antes de prometer agendamento ao cliente |
| Permissões grossas | Só BAAS_READ e BAAS_ADMIN. Quem configura provedor também transfere dinheiro | Separar exige permissões novas no vocabulário do IAM |
BAAS_READ vê CPF sem máscara | Não há mascaramento de documento nas respostas | Trate BAAS_READ como permissão sensível |
| KYC limitado a 1 MB por requisição | bodyLimit global. Documento em base64 estoura facilmente | Use URL do File Storage em vez de base64 |
| Sem expurgo para LGPD | Contas e transações não têm exclusão lógica nem rotina de eliminação | Eliminação é processo manual |
| Config com credencial rotacionada não tem período de graça | PATCH com credentials substitui na hora. Requisição em voo com a credencial antiga falha | Coordene a janela |
| Sem cache de saldo | Toda consulta bate no provedor. Volume alto de consulta consome quota da API do banco | Por design (§7) — mas planeje a quota |
| Estorno só de Pix recebido e completo | POST /transfers/:id/refund exige PIX_IN e COMPLETED. Não há devolução de Pix enviado nem estorno de TED | Restrição do fluxo Pix, verificada antes de chamar o provedor |
CANCELLED não é alcançável | O status existe no enum, mas nenhum caminho do código leva a ele | Reservado para status vindo do provedor |
| MockBank compartilha estado no processo | O simulador guarda estado em estruturas estáticas por processo, não segregadas por organização. Duas organizações usando MockBank no mesmo processo enxergam o mesmo DICT | Aceitável para desenvolvimento; é mais um motivo para nunca usá-lo em produção |
| Escrita local depois da confirmação do provedor | Se o provedor aceitar e a gravação local falhar, existe transação no banco parceiro sem contrapartida aqui | Reconcilie por endToEndId — é a lição do caso Synapse (§4) |
Não deployado como dependência de outro building block. Nenhum outro módulo consome o BaaS por ModuleClient. A integração com o resto do catálogo é por evento e pelo personId. Isso é coerente com o desenho, mas significa que o BaaS ainda não tem tráfego vindo de dentro da plataforma.
16Perguntas frequentes
A Catalisa é um banco? Vocês têm licença do Banco Central?
Não, e a resposta é curta de propósito. A Catalisa é a camada de software. Quem tem licença, custodia o dinheiro e responde ao Banco Central é o provedor que você contrata — Celcoin, QI Tech ou FitBank. Nós não somos instituição autorizada, não custodiamos recurso de terceiro e não somos participantes do Pix. Se alguém apresentar isto de outra forma numa proposta, a proposta está errada. §14 detalha o que isso implica em contrato.
Preciso ter contrato com um banco antes de começar a integrar?
Não. Use o MockBank: ele simula conta, saldo, DICT, transferência, QR Code e webhook, e a esteira que você constrói contra ele é a mesma que roda em produção. Na prática, o time de engenharia costuma terminar a integração antes de o jurídico terminar o contrato. Só não coloque MockBank em produção — o banco 999 não existe.
Dá para trocar de provedor depois? Quanto custa?
Dá, e é o motivo pelo qual este building block existe. Você cria a configuração nova, valida com /test, marca como padrão, e as contas novas nascem no provedor novo. As antigas continuam no antigo, funcionando, porque cada conta guarda o configId a que pertence. O que não dá é mover uma conta existente de um banco para outro — a conta é do banco. Migrar de verdade significa encerrar lá e abrir cá, com o titular no meio.
Posso usar dois provedores ao mesmo tempo?
Pode, inclusive dois do mesmo tipo com credenciais diferentes. É o mecanismo de migração gradual, e também serve para segregar operações — por exemplo, uma configuração para contas escrow e outra para a conta operacional.
O saldo que a API retorna é confiável?
É o saldo do provedor, consultado na hora. GET /accounts/:id/balance sempre bate no banco antes de responder. As colunas availableBalance e blockedBalance que aparecem na listagem de contas são cache atualizado de forma assíncrona — servem para tela de listagem, não para decidir se uma transferência pode sair.
Uma transferência ficou presa em PROCESSING. O que faço?
Chame GET /baas/api/v1/pix/transfers/:id. Esse endpoint não é passivo: ele consulta o provedor e grava o status novo se mudou. É o caminho de reconciliação para webhook perdido. Não há varredura automática hoje (§15), então vale agendar a sua — a receita está em §11.
Como faço para o mesmo Pix não sair duas vezes se minha chamada der timeout?
Hoje, controlando do seu lado. O campo idempotencyKey é aceito pelo schema mas não é repassado ao provedor (§15). Até isso ser corrigido, mantenha um registro do seu identificador de operação antes de chamar e verifique-o antes de reenviar. É a limitação mais relevante para quem vai operar com dinheiro real, e está documentada aqui exatamente por isso.
Quais eventos eu recebo, e como?
São 18 eventos baas.*, listados em §8. Configure clientWebhookUrl e clientWebhookEvents na configuração do provedor; o BaaS cria a assinatura no Webhooks Engine, que faz a entrega com retry e assinatura. Confira o campo webhookSubscriptionId na resposta — se vier vazio, a assinatura não foi criada.
Qual a diferença entre o BaaS e o Open Finance?
Direção oposta. O Open Finance lê dados de contas que o seu cliente tem em outras instituições, com consentimento. O BaaS opera uma conta que existe por causa da sua aplicação. Um enxerga; o outro movimenta.
Por que uma conta de outra organização devolve 404 e não 403?
Porque 403 responderia uma pergunta que ninguém deveria poder fazer: "este ID existe em algum lugar?". Em building block financeiro, confirmar a existência de uma conta alheia já é vazamento. O 404 é a resposta correta para "não existe para você".
Posso rodar isso em produção hoje?
O módulo está deployado nas stacks de staging e produção, e a esteira funciona. Mas antes de mover dinheiro real: resolva a verificação de assinatura de webhook do seu provedor e ligue PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT=on; trate a idempotência do lado da sua aplicação; e homologue contra o sandbox real do provedor, o que este repositório ainda não registra ter sido feito. §15 é a lista completa, e ela existe para ser lida antes da decisão, não depois.
Padrão: PADRAO-DOCUMENTACAO.md · Decisão de arquitetura: ADR-0003 · Detalhes de mapeamento por provedor: providers/README.md · Índice: INDEX.md