Catalisa.
Building blocks/FinanceiroBeta

BaaS

Contas, Pix e TED por API, com o banco parceiro trocável sem reescrever a integração

31
Endpoints
9
Entidades
3
Provedores
Tenant
Escopo
3015
Porta

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.

Para quem é
  • 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ó
Substitui
  • 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
O que não é
  • 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.

AtributoValor
Identificadorbaas
CategoriaFinanceiro
EscopoTenant (exige organizationId no token)
Porta (standalone)3015
Path alias@baas
Prefixo HTTP/baas
Schema no bancobaas
StatusBeta — deployado desde 2026-01
Depende dePostgreSQL, 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 campo Method no 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

AntesDepois
Uma integração artesanal por provedor bancárioUma API canônica; o provedor é configuração da organização
Trocar de banco parceiro é projeto de mesesCriar uma configuração nova e apontar as contas seguintes para ela
Só dá para começar depois do contrato com o bancoMockBank simula conta, saldo, DICT, Pix e QR Code desde o primeiro dia
Cada provedor exige um formato de webhook diferenteTodos os webhooks viram o mesmo conjunto de 18 eventos internos
Credenciais de banco em variável de ambienteCredenciais 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 verdadeavailableBalance 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érioCatalisa BaaSCelcoinQI TechDockUnit (EUA)
Carrega licença do BACENNão — camada técnicaSim (IP + SCD)Sim (SCD)Sim (IP, part. direto Pix)Não (bancos parceiros, EUA)
Custodia recurso de clienteNãoSimSimSimNão
Provedor trocável sem reescreverSim, por configuraçãoNão se aplicaNão se aplicaNão se aplicaNão
Mais de um provedor simultâneoSim, por organizaçãoNãoNãoNãoNão
Ambiente de teste sem contratoSim (MockBank completo)Sandbox após contratoSandbox após contratoSandbox após contratoSandbox
Pix, TED e DICTSim, via provedorSimSimSimNão (mercado EUA)
Emissão de cartãoNãoSimSimSimSim
Crédito / originaçãoNão (é o Decision Platform)ParcialSimParcialSim
BoletoNão implementado (§15)SimSimSimNão
Preço públicoPrecificação em definiçãoNão divulgadoNão divulgadoNão divulgadoNão divulgado

Nossos diferenciais

  1. O provedor é uma linha no banco de dados. BaaSProviderConfig.providerType mais 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.
  2. Coexistência de provedores, não apenas suporte a vários. A distinção importa: suportar N provedores é escolher um por cliente. Aqui BaaSAccount.configId amarra 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.
  3. 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, endToEndId no 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.
  4. Isolamento por organização em toda consulta de escrita. Cada serviço compara organizationId do token com o do registro antes de agir, e devolve 404 — não 403 — 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:

DriverPor quê
Contas ativasO provedor cobra por conta aberta e mantida; é o custo que cresce com a base
Transações Pix e TED processadasCusto por transação do provedor, repassado
Chamadas de API ao provedorConsulta 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 provedorCatalisa BaaS
Custo transacionalDo provedor, negociadoDo provedor, negociado — o mesmo
Custo de integração inicialUma integração completa por provedorUma integração; o provedor entra por configuração
Custo de trocar de provedorNova integração completaNova configuração; contas antigas continuam no provedor antigo
Custo de ter dois provedoresDuas integrações completas e mantidasDuas configurações
Custo de testar antes do contratoBloqueado até liberação de sandboxZero — 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. getProviderForAccount busca a config, decifra as credenciais e chama createBaaSProvider a 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/balance sempre bate no provedor. O campo availableBalance no 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 PENDING no 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 por endToEndId precisa cobrir (§15).
  • Tipos canônicos, e não o formato do provedor, atravessam o serviço. CanonicalAccount, CanonicalPixTransaction, CanonicalBalance e companhia são o contrato interno. Nenhum serviço sabe que o FitBank usa TaxNumber como 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ão externalId sozinho — 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-Id ou do query param organizationId que 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_transactions e mockbank_qr_codes são o "banco de dados do banco simulado", separadas das tabelas baas_* 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

TermoSignifica
Provider configConfiguraçã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.
ProviderImplementação da interface BaaSProvider para um banco específico. Instanciada por requisição a partir da config.
Tipo canônicoFormato interno neutro (CanonicalAccount, CanonicalPixTransaction, ...) para o qual todo provedor traduz. É o contrato que isola o serviço do banco.
externalIdIdentificador do recurso no provedor. Único por (configId, externalId). É a chave de correlação com o mundo externo.
endToEndIdIdentificador ú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.
DICTDiretó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 EVPChave Pix aleatória, em formato UUID. É a única que o BaaS gera sem você informar valor.
EMV / BR CodeFormato do payload de QR Code Pix. O campo emvCode é o "copia e cola".
KYCVerificação de identidade do titular. O BaaS encaminha documentos ao provedor e reflete o status; a análise é do provedor.
MockBankProvedor 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 PrismaTabelaPropósitoCampos-chave
BaaSProviderConfigbaas.baas_provider_configsProvedor configurado para a organizaçãoproviderType, credentials (cifrado), isDefault, isActive, settings, único (organizationId, name)
BaaSAccountbaas.baas_accountsConta bancária mantida no provedorexternalId, configId, status, bankCode, branchNumber, accountNumber, availableBalance, personId, businessId, único (configId, externalId)
BaaSPixKeybaas.baas_pix_keysChave Pix registrada para uma contakeyType, keyValue, status, deletedAt, único (accountId, keyType, keyValue)
BaaSTransactionbaas.baas_transactionsTransação Pix ou TED, entrada ou saídatransactionType, status, amount Decimal(15,2), endToEndId, isCredit, originalTransactionId, único (accountId, externalId)
BaaSWebhookEventbaas.baas_webhook_eventsEvento cru recebido do provedoreventType, externalEventId, payload, processed, error, único (configId, externalEventId)
MockBankAccountbaas.mockbank_accountsEstado interno do banco simuladoexternalId, saldo, dados do titular, kycStatus
MockBankPixKeybaas.mockbank_pix_keysDICT do banco simuladokeyValue único globalmente
MockBankTransactionbaas.mockbank_transactionsExtrato do banco simuladoendToEndId, type, status
MockBankQrCodebaas.mockbank_qr_codesQR Codes emitidos pelo banco simuladoemvCode, expiresAt

O único (configId, externalEventId) em baas_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

EnumValores
BaaSProviderTypeCELCOIN · FITBANK · QITECH · MOCKBANK
BaaSAccountStatusPENDING_KYC · KYC_IN_PROGRESS · KYC_REJECTED · ACTIVE · BLOCKED · CLOSED
BaaSTransactionTypePIX_IN · PIX_OUT · TED_IN · TED_OUT · REVERSAL
BaaSTransactionStatusPENDING · PROCESSING · COMPLETED · FAILED · CANCELLED · REVERSED
BaaSPixKeyTypeCPF · CNPJ · EMAIL · PHONE · EVP
BaaSPixKeyStatusPENDING · ACTIVE · INACTIVE
KycStatusNOT_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.*.

GrupoEventos
Contabaas.account.created · baas.account.updated · baas.account.closed · baas.account.blocked
KYCbaas.kyc.submitted · baas.kyc.approved · baas.kyc.rejected
Pixbaas.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
TEDbaas.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étodoRotaDescriçãoPermissão
POST/baas/api/v1/provider-configsCria config, testa a conexão e cifra as credenciais. 201BAAS_ADMIN
GET/baas/api/v1/provider-configsLista, paginado. Filtros filter[providerType], filter[isActive]BAAS_READ
GET/baas/api/v1/provider-configs/:idBusca por IDBAAS_READ
PATCH/baas/api/v1/provider-configs/:idAtualiza. Se enviar credentials, testa antes de gravarBAAS_ADMIN
DELETE/baas/api/v1/provider-configs/:idExclusão lógica. 204BAAS_ADMIN
POST/baas/api/v1/provider-configs/:id/set-defaultMarca como padrão e desmarca as outrasBAAS_ADMIN
POST/baas/api/v1/provider-configs/:id/testTesta a conexão com as credenciais gravadasBAAS_ADMIN

Contas — /baas/api/v1/accounts

MétodoRotaDescriçãoPermissão
POST/baas/api/v1/accountsAbre conta no provedor e registra localmente. 201BAAS_ADMIN
GET/baas/api/v1/accountsLista, paginado. Filtros filter[status], filter[personId], filter[configId]BAAS_READ
GET/baas/api/v1/accounts/:idBusca por IDBAAS_READ
PATCH/baas/api/v1/accounts/:idAtualiza titular no provedor e/ou metadata localBAAS_ADMIN
POST/baas/api/v1/accounts/:id/closeEncerra a conta. Exige reasonBAAS_ADMIN
GET/baas/api/v1/accounts/:id/balanceSaldo consultado no provedor a cada chamadaBAAS_READ
POST/baas/api/v1/accounts/:id/kycEnvia documentos de KYCBAAS_ADMIN
GET/baas/api/v1/accounts/:id/kycConsulta o status de KYC no provedorBAAS_READ
POST/baas/api/v1/accounts/:id/sandbox/depositSó MockBank. Credita saldo para testeBAAS_ADMIN

Pix — /baas/api/v1/pix

MétodoRotaDescriçãoPermissão
POST/baas/api/v1/pix/keysRegistra chave Pix. 201BAAS_ADMIN
GET/baas/api/v1/pix/keysLista chaves. Exige ?accountId=, senão 400BAAS_READ
DELETE/baas/api/v1/pix/keys/:idRemove a chave no provedor e marca deletedAt. 204BAAS_ADMIN
POST/baas/api/v1/pix/keys/lookupConsulta o DICT: quem é dono de uma chaveBAAS_READ
GET/baas/api/v1/pix/transfersLista transações Pix da organização, com dados da contaBAAS_READ
POST/baas/api/v1/pix/transfersInicia transferência Pix. 201BAAS_ADMIN
GET/baas/api/v1/pix/transfers/:idConsulta e sincroniza o status com o provedorBAAS_READ
POST/baas/api/v1/pix/transfers/:id/refundDevolve um Pix recebido. 201BAAS_ADMIN
POST/baas/api/v1/pix/qr-codes/generateGera QR Code estático ou dinâmicoBAAS_ADMIN
POST/baas/api/v1/pix/qr-codes/decodeDecodifica um código EMVBAAS_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étodoRotaDescriçãoPermissão
POST/baas/api/v1/tedInicia transferência TED. 201BAAS_ADMIN
GET/baas/api/v1/tedLista transações TED. Aceita ?accountId= ou os mesmos filtros do PixBAAS_READ
GET/baas/api/v1/ted/:idConsulta e sincroniza o status com o provedorBAAS_READ

GET /ted muda de comportamento conforme o filtro: com accountId e sem configId, 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étodoRotaDescriçãoAutenticação
POST/baas/api/v1/webhooks/:providerTypeRecebe webhook do provedor. :providerType é celcoin, fitbank, qitech ou mockbankAssinatura do provedor — não usa JWT
GET/baas/api/v1/webhooks/healthSonda simples do subsistema de webhookPública

Saúde

MétodoRotaDescrição
GET/baas/healthIdentificaçã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"]
}
CampoTipoObrigatórioDescrição
namestring (1–100)SimÚnico dentro da organização. Colisão devolve 409
providerTypeCELCOIN | FITBANK | QITECH | MOCKBANKSimDetermina qual estratégia é instanciada
credentialsRecord<string,string>SimFormato depende do provedor — ver §13. Cifrado antes de gravar
isDefaultbooleanNãoMarcar como padrão desmarca a anterior
isActivebooleanNãoPadrão true. Config inativa não pode virar padrão
settingsobjectNãoAjustes específicos do provedor
webhookUrlstring (URL)NãoOnde o provedor entrega. Omitido, o serviço gera a partir de BAAS_WEBHOOK_BASE_URL
webhookEventsstring[]NãoFiltra o que é processado. Vazio significa todos
clientWebhookUrlstring (URL)NãoOnde você quer receber. Cria uma assinatura no Webhooks Engine
clientWebhookEventsstring[]NãoFiltros 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

StatusQuando
400Corpo reprovado no Zod, credenciais incompletas, ou testConnection() falhou
403Token sem organizationId, ou sem BAAS_ADMIN
409Já 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"
    }
  }
}
CampoTipoObrigatórioDescrição
configIduuidNãoConfig a usar. Omitido, usa a padrão
personIduuidNãoVínculo com Person do Customers. Não é chave estrangeira — ver §15
businessIdstringNãoSeu identificador de negócio. Fica na conta e nas transações
holder.documentTypeCPF | CNPJSimDefine quais campos o provedor vai exigir
holder.documentNumberstring (11–14)SimSem formatação
holder.legalRepresentativeobjectNãoPara CNPJ: representante legal
metadataobjectNãoChaves 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"
}
CampoTipoObrigatórioDescrição
accountIduuidSimConta de origem. Precisa ser da sua organização
amountnumber > 0SimEm reais, com centavos. Precisão de Decimal(15,2)
descriptionstring (≤140)NãoVai no campo livre do Pix
pixKeystringCondicionalChave do destinatário. Celcoin e FitBank consultam o DICT antes de enviar
recipientobjectCondicionalDados bancários completos, quando não há chave
scheduledForstring (ISO 8601)NãoAgendamento. Suporte depende do provedor
businessIdstringNãoSeu identificador, guardado na transação
idempotencyKeyuuidNãoAceito pelo schema, mas hoje não repassado ao provedor — ver §15

Resposta 201data.attributes traz transactionType: "PIX_OUT", status, amount, fee, endToEndId, os campos counterparty* e processedAt.

Erros

StatusQuando
400Sem pixKey nem recipient; saldo insuficiente; valor acima do limite; conta bloqueada ou encerrada
403Token sem organizationId ou sem BAAS_ADMIN
404accountId 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:

  1. Valida que :providerType é conhecido.
  2. Localiza a config ativa daquele tipo, na organização.
  3. verifyWebhookSignature() — comportamento controlado por PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT (§13).
  4. parseWebhookPayload() traduz o corpo do provedor para ParsedBaaSWebhook.
  5. Filtra por webhookEvents da config; evento não assinado é descartado com { received: true }.
  6. Deduplica por (configId, externalEventId); repetido é descartado com { received: true }.
  7. Grava em baas_webhook_events, aplica o efeito e publica o evento baas.*.

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 configId da 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-default afeta 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).
  • GET bate 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 o id da transação nem o externalId. É 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.

  • expiresInMinutes tem teto de 1440 — 24 horas. Acima disso o Zod reprova com 400.
  • Sem amount, o QR é estático: reutilizável e sem valor definido. Com amount, é 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 PATCH registra um warn no log e devolve 200 mesmo assim, com webhookSubscriptionId vazio. 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: APPROVED leva a conta a ACTIVE, REJECTED a KYC_REJECTED, e qualquer outra coisa a KYC_IN_PROGRESS.
  • Quem decide é o provedor. O BaaS não avalia documento; ele encaminha e reflete. rejectedFields diz o que reenviar.
  • Conta sem KYC aprovado não transaciona. No MockBank isso é explícito (KYC_NOT_APPROVED vira 403); 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 blockComo se relacionaObrigatório
IAMEmite o token; organizationId, BAAS_READ e BAAS_ADMIN vêm deleSim
Webhooks EngineProviderConfigService cria, atualiza e apaga a assinatura de saída quando você define clientWebhookUrlSim — é dependência de container
CustomersBaaSAccount.personId aponta para Person; filter[personId] lista as contas de uma pessoaNão
Audit TrailConsome os eventos baas.* para trilha de complianceNão
File StorageGuarda os documentos de KYC; o BaaS recebe a URL, não o arquivoNão
PaymentsCobrança e conciliação de recebíveis; o BaaS é a conta onde o dinheiro caiNão
Open FinanceAgrega dados de contas em outras instituições; o BaaS opera a conta que é suaNão
Decision PlatformDecide se concede o crédito; o BaaS desembolsa o resultadoNã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ávelDescriçãoObrigatóriaPadrão
BAAS_CREDENTIAL_MASTER_KEYChave AES-256-GCM das credenciais de provedor. Exatamente 64 caracteres hexadecimais (32 bytes). Gere com openssl rand -hex 32Sim, para usar o módulo
BAAS_WEBHOOK_BASE_URLBase das URLs de callback geradas automaticamenteNãohttp://localhost:3000
PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENToff ou on. Com on, webhook que não puder ser verificado é rejeitadoNãooff
DATABASE_URLPostgreSQLSim
REDIS_URLRedis, usado no rate limitSim
JWT_SECRETVerificação do token do IAM. Mínimo 44 caracteresSim
MODULE_IAM_URLURL do IAM em standaloneEm standalone
DEPLOYMENT_MODEmonolith ou standaloneNãomonolith
PORTPorta em standaloneNão3000 (mapeada para 3015)

BAAS_CREDENTIAL_MASTER_KEY nã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 .env versionado.

Credenciais por provedor — o que vai dentro do campo credentials.

ProvedorCamposAutenticaçãoAmbientes
CELCOINclientId, clientSecret, sandboxOAuth2 client_credentials, token em cache até 5 min antes de expirarsandbox.openfinance.celcoin.dev · api.celcoin.com.br
FITBANKapiKey, apiSecret, partnerId, businessUnitId, sandboxBasic Authsandboxapi.fitbank.com.br/main/execute · api.fitbank.com.br/main/execute
QITECHapiKey, privateKey, sandboxRequisição assinada com RSAsandbox.qitech.app · api-auth.qitech.app
MOCKBANKtoken, autoApproveKyc, simulateDelays, delayMsSó confere que token não está vazioNenhum — 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ênciaPara quê
PostgreSQLSchema baas — configs, contas, chaves, transações, eventos e o estado do MockBank
RedisContadores de rate limit
IAMValidação do token e resolução de permissões
Webhooks EngineAssinaturas de entrega dos eventos baas.*
API do provedorToda operação bancária. Indisponibilidade do banco é indisponibilidade da operação

Limites

LimiteValorOnde
Corpo da requisição1 MBapplyCommonMiddleware — afeta KYC em base64
Rate limit global10.000 req / 60sRATE_LIMIT_GLOBAL_MAX
Descrição de Pix e TED140 caracteresZod
Expiração de QR dinâmico1 a 1440 minutosZod
Página padrão / listagens20 itensRouters
Precisão monetáriaDecimal(15,2)Prisma
Limites de valor por transaçãoDo provedorMockBank: 100.000 (Pix) e 500.000 (TED)

Catálogo de erros

StatusCódigoSignificaO que fazer
400(árvore Zod)Corpo reprovado no schemaCompare os campos com §9
400VALIDATIONRegra de negócio: sem saldo, sem pixKey nem recipient, conta já encerrada, estorno de transação que não é PIX_IN COMPLETEDA message diz qual regra
400VALIDATIONtestConnection() falhou ao criar ou atualizar configConfira as credenciais e o ambiente (sandbox)
400Webhook sem X-Organization-Id nem organizationIdCorrija a URL cadastrada no painel do provedor
401UNAUTHORIZEDToken ausente, inválido ou expiradoRenove no IAM
403Token sem organizationIdAutentique informando a organização
403FORBIDDENSem BAAS_READ ou BAAS_ADMIN; ou conta bloqueada, encerrada, ou sem KYC aprovadoConfira permissões e o status da conta
404NOT_FOUNDRecurso inexistente ou de outra organizaçãoConfira o ID e a organização do token
409CONFLICTNome de config já usado na organizaçãoEscolha outro nome
429Rate limitRecuo exponencial
500INTERNALErro não mapeado do provedorConsulte o log; o erro cru do provedor fica lá

Observabilidade.

  • GET /baas/health devolve 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 warn estruturado com o provedor e o motivo, tanto com enforcement ligado quanto desligado. É o log que diz se você pode ligar PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT=on sem 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 = false acumulando é 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/balance sempre 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:

  1. Assinatura do provedor, verificada por verifyWebhookSignature() de cada provedor.
  2. PROVIDER_WEBHOOK_SIGNATURE_ENFORCEMENT, que decide o que fazer quando a verificação não é possível: off aceita e registra warn; on rejeita. 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 do warn. §15 lista quais provedores hoje conseguem verificar de fato.
  3. 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_READ vê o CPF do titular. Trate BAAS_READ como 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á deletedAt em baas_accounts nem em baas_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.

ProvedorSituação real, verificada no códigoO que falta
MockBankImplementaçã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 webhookNada — mas é simulador. Nunca use em produção. Banco 999 e ISPB 99999999 são fictícios
CelcoinOs 24 métodos da interface implementados contra a API REST real, com OAuth2 e cache de token. Testado com HTTP mockadoVerificaçã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 TechOs 24 métodos implementados, com requisição assinada por RSA. Testado com HTTP mockadoVerificação de assinatura de webhook não implementada. Sem evidência de homologação contra o sandbox real neste repositório
FitBank / FitsOs 24 métodos implementados sobre a API RPC. Testado com HTTP mockadoO 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çãoImpactoSituação
idempotencyKey aceito mas ignoradoOs 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 duplicadaLacuna funcional conhecida. Enquanto não for corrigida, faça controle de duplicidade do seu lado antes de chamar
Sem reconciliação automáticaWebhook perdido deixa a transação parada em PENDING ou PROCESSING indefinidamente. Não há job de varreduraFaça o polling você — receita em §11
Boleto não implementadoNão há emissão nem baixa de boleto, embora os provedores ofereçamFora do escopo atual
Bloquear e desbloquear conta não tem rota HTTPblockAccount e unblockAccount existem na interface e nos quatro provedores, mas nenhum router os expõe. O bloqueio só chega por webhook do provedorImplementado na camada de provedor, não exposto
personId não é chave estrangeiraO vínculo com Person do Customers é um UUID solto: sem validação na criação, sem integridade referencial, sem cascataPor design de desacoplamento entre building blocks — mas o personId inválido só aparece quando alguém tenta usar
Agendamento sem garantia uniformescheduledFor é aceito e repassado, mas o comportamento é do provedor. O MockBank ignora e executa na horaConfirme com o seu provedor antes de prometer agendamento ao cliente
Permissões grossasBAAS_READ e BAAS_ADMIN. Quem configura provedor também transfere dinheiroSeparar exige permissões novas no vocabulário do IAM
BAAS_READ vê CPF sem máscaraNão há mascaramento de documento nas respostasTrate BAAS_READ como permissão sensível
KYC limitado a 1 MB por requisiçãobodyLimit global. Documento em base64 estoura facilmenteUse URL do File Storage em vez de base64
Sem expurgo para LGPDContas e transações não têm exclusão lógica nem rotina de eliminaçãoEliminação é processo manual
Config com credencial rotacionada não tem período de graçaPATCH com credentials substitui na hora. Requisição em voo com a credencial antiga falhaCoordene a janela
Sem cache de saldoToda consulta bate no provedor. Volume alto de consulta consome quota da API do bancoPor design (§7) — mas planeje a quota
Estorno só de Pix recebido e completoPOST /transfers/:id/refund exige PIX_IN e COMPLETED. Não há devolução de Pix enviado nem estorno de TEDRestrição do fluxo Pix, verificada antes de chamar o provedor
CANCELLED não é alcançávelO status existe no enum, mas nenhum caminho do código leva a eleReservado para status vindo do provedor
MockBank compartilha estado no processoO 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 DICTAceitável para desenvolvimento; é mais um motivo para nunca usá-lo em produção
Escrita local depois da confirmação do provedorSe o provedor aceitar e a gravação local falhar, existe transação no banco parceiro sem contrapartida aquiReconcilie 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 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

Building blocks relacionados