Ledger double-entry: como desenvolvemos um ledger que nunca perde um centavo
Cada transação na Revenu gera lançamentos automáticos de partida dobrada. Veja como nosso ledger de 11 engines lida com multi-moeda, compliance COSIF, reconciliação em tempo real, e por que a maioria dos ledgers fintech são bombas-relógio. A arquitetura por trás de um ledger que processou R$ 8 bilhões com zero discrepâncias de saldo.

Por que partida dobrada importa em fintech — e por que a maioria das fintechs erra
Sistemas de partida simples rastreiam saldos. Sistemas de partida dobrada rastreiam o movimento de valor. Para instituições financeiras reguladas, isso não é opcional — é obrigatório.
Mas aqui está o que a maioria dos engenheiros fintech não entende: a contabilidade de partida dobrada não é apenas um checkbox regulatório. É o único sistema contábil que é auto-verificável. Cada débito tem um crédito correspondente. Se o total de débitos não é igual ao total de créditos, algo está errado — e você consegue encontrar.
Sistemas de partida simples (que é o que a maioria das fintechs realmente constrói, mesmo que chamem de "ledger") não conseguem te dizer quando algo está errado. Eles rastreiam um saldo: +R$ 100, -R$ 50, saldo = R$ 50. Se um bug soma R$ 100 sem subtrair de algum lugar, o saldo diz R$ 150 e ninguém sabe que há um problema até que um relatório de reconciliação pegue — dias ou semanas depois.
Em um sistema de partida dobrada, esse bug é impossível. Você não pode creditar uma conta sem debitar outra. O sistema impõe conservação de valor no nível da transação, não como uma verificação posterior.
A epidemia de ledgers fintech: tabelas de saldo fingindo ser ledgers
Deixa eu descrever o que 80% das fintechs chamam de "ledger":
Uma tabela PostgreSQL chamada balances com colunas: account_id, currency, available, pending, blocked. Quando um pagamento chega, o sistema roda UPDATE balances SET available = available + 100 WHERE account_id = 'abc'.
Isso não é um ledger. É um rastreador de saldo. E tem falhas fatais:
Sem trilha de auditoria
UPDATE sobrescreve o valor anterior. Uma vez que o saldo muda de R$ 500 para R$ 600, não há registro do estado R$ 500 a menos que você logue separadamente. A maioria não faz.
Condições de corrida
Duas transações concorrentes leem available = 500, ambas somam R$ 100, ambas escrevem R$ 600. A conta deveria ter R$ 700 mas tem R$ 600. Você acabou de criar R$ 100 do nada — ou destruir, dependendo da operação.
Sim, SELECT FOR UPDATE e locks de linha ajudam. Mas serializam todas as operações em uma única conta, criando um gargalo de performance que limita throughput a ~1.000 TPS por conta.
Sem capacidade de reconciliação
Como você verifica que o saldo está correto? Precisaria re-executar todas as transações históricas. Mas você não as tem — só tem o saldo atual. Você não consegue provar que o saldo está certo porque jogou fora a evidência.
Erros de arredondamento se acumulam silenciosamente
Quando você calcula uma taxa de plataforma de 12,5% sobre R$ 99,99 (= R$ 12,49875), arredonda para R$ 12,50. Ao longo de milhões de transações, esses erros de arredondamento se acumulam. Em um sistema de partida simples, ninguém nota até que a reconciliação de fim de trimestre revela uma discrepância de R$ 47.000. Boa sorte explicando isso para o auditor.
A arquitetura de ledger da Revenu: os 11 engines
Nosso ledger não é uma tabela ou mesmo um serviço único. É um sistema de 11 engines especializados, cada um responsável por um domínio específico de contabilidade financeira:
1. Core Ledger Engine
A fundação. Lida com lançamentos de partida dobrada, impõe a equação contábil (Ativos = Passivos + Patrimônio Líquido), e garante que cada transação balancea em zero.
Cada lançamento é um par: DÉBITO conta_a, CRÉDITO conta_b, valor, moeda. Operações complexas (como splits) criam múltiplos pares de lançamentos — todos dentro de uma única transação atômica.
2. Chart of Accounts Engine
Gerencia a estrutura hierárquica de contas. Cada conta na Revenu é classificada segundo um plano de contas que mapeia para o COSIF (Plano Contábil das Instituições do Sistema Financeiro Nacional) — a classificação contábil obrigatória do BACEN.
Hierarquia de contas exemplo:
- 1.0.0.00.00-0 ATIVO
- 1.1.0.00.00-0 Disponibilidades
- 1.1.1.00.00-0 Reservas bancárias
- 1.3.0.00.00-0 Operações interfinanceiras
- 1.3.1.00.00-0 Clearing PIX
- 1.3.2.00.00-0 Clearing TED
Esse mapeamento significa que cada lançamento interno automaticamente classifica na conta COSIF correta. Reporting CADOC se torna uma projeção de dados existentes, não um exercício de mapeamento manual.
3. Multi-Currency Engine
Lida com contas denominadas em diferentes moedas, rastreamento de taxas de câmbio e lançamentos de conversão de moeda.
Quando um pagamento cross-border converte USD para BRL, o engine cria:
DÉBITO: conta_usd_cliente USD 1.000,00
CRÉDITO: clearing_fx_usd USD 1.000,00
DÉBITO: clearing_fx_brl BRL 5.120,00
CRÉDITO: conta_brl_cliente BRL 5.120,00
O spread de FX (se houver) é capturado como um lançamento separado na conta de receita de câmbio da plataforma. O ledger rastreia a taxa de câmbio exata usada, o timestamp da conversão e a fonte da taxa — tudo imutavelmente.
4. Escrow Engine
Gerencia contas escrow para splits de marketplace, retenções de pagamento e cronogramas de liquidação. Cada entrada de escrow é uma conta de ledger separada com seu próprio ciclo de vida (PENDING → AVAILABLE → SETTLED ou REVERSED).
5. Fee Engine
Calcula e lança taxas de plataforma, MDR, interchange, custos de gateway e taxas de antecipação. Cada taxa é um par de lançamentos separado — nunca uma dedução silenciosa de um saldo.
Quando um seller recebe R$ 1.000 com MDR de 3%:
DÉBITO: clearing_comprador R$ 1.000
CRÉDITO: escrow_seller R$ 970
CRÉDITO: receita_mdr_plataforma R$ 30
O seller vê R$ 970. A plataforma vê R$ 30 em receita de taxas. A conta clearing balancea em zero. Três lançamentos, uma transação atômica, trilha de auditoria perfeita.
6. Provisioning Engine
Lida com provisões para perdas esperadas — provisões de perda de crédito, provisões de chargeback e requisitos de capital regulatório. Provisões são revertidas quando a perda esperada não se materializa ou ajustadas quando perdas reais diferem das projeções.
7. Accrual Engine
Gerencia reconhecimento de valor baseado em tempo: acumulação de juros em empréstimos, reconhecimento de taxas ao longo de períodos de serviço e receita diferida. O engine roda jobs de accrual diários que criam lançamentos automaticamente baseados em regras configuradas.
8. Tax Engine
Calcula e lança IOF (Imposto sobre Operações Financeiras), IRRF (Imposto de Renda Retido na Fonte), ISS (Imposto Sobre Serviços) e contribuições PIS/COFINS. Lançamentos tributários são separados da transação subjacente — se as regras tributárias mudam, lançamentos históricos permanecem intocados e novas regras se aplicam dali em diante.
9. Reconciliation Engine
Reconcilia continuamente o estado do ledger interno contra fontes externas: extratos bancários (CNAB 240 / camt.053), relatórios de liquidação de PSTIs, arquivos de liquidação de bandeiras de cartão e relatórios de clearing de boletos.
O engine roda reconciliação como um processo streaming, não como job batch. Cada evento externo é pareado contra lançamentos internos em tempo real. Itens não pareados são sinalizados imediatamente.
10. Reporting Engine
Gera demonstrações financeiras COSIF-compliant, balanços patrimoniais, demonstrações de resultado e relatórios de fluxo de caixa — tudo a partir de dados do ledger. Porque cada lançamento mapeia para uma conta COSIF, relatórios são projeções de dados existentes, não cálculos separados.
11. Archive Engine
Lida com o ciclo de vida de dados do ledger: compressão de lançamentos históricos, offloading para cold storage e recuperação garantida para solicitações de auditoria. Requisito regulatório: 10 anos de histórico completo de ledger, acessível em até 48 horas.
O princípio de imutabilidade: por que nunca fazemos UPDATE
Cada entrada de ledger na Revenu é imutável. Uma vez escrita, não pode ser modificada ou deletada. Nunca.
Isso não é só boa prática — é um invariante arquitetural imposto em cada camada:
Nível de banco de dados
As tabelas de ledger não têm permissões de UPDATE ou DELETE. O usuário de banco de dados da aplicação só pode fazer INSERT e SELECT. Mesmo DBAs com acesso superuser disparariam alertas se tentassem um UPDATE em tabelas de ledger.
Nível de aplicação
O serviço de ledger expõe apenas duas operações: post(entries) e query(filters). Não existe endpoint de update ou delete. A API literalmente não consegue modificar entradas existentes.
Mecanismo de correção
O que acontece quando algo está errado? Você não corrige a entrada errada — cria uma nova entrada de reversão que a neutraliza, e depois cria uma nova entrada corrigida.
Original (errada): DÉBITO: conta_a R$ 100, CRÉDITO: conta_b R$ 100
Reversão: DÉBITO: conta_b R$ 100, CRÉDITO: conta_a R$ 100
Corrigida: DÉBITO: conta_a R$ 110, CRÉDITO: conta_b R$ 110
A trilha de auditoria agora mostra: a entrada original, que foi revertida (com código de motivo e timestamp), e a entrada corrigida. Um auditor consegue ver exatamente o que aconteceu, quando e por quê. Nada é escondido.
Event Sourcing + partida dobrada: um match natural
Nosso ledger é construído sobre Event Sourcing. Cada operação financeira na Revenu emite eventos de domínio. O ledger é uma projeção desses eventos.
PaymentReceived { amount: 1000, from: buyer, to: clearing }
→ DÉBITO: conta_comprador R$ 1.000 / CRÉDITO: conta_clearing R$ 1.000
SplitExecuted { recipients: [{ seller_a: 400 }, { seller_b: 350 }, { seller_c: 250 }], fee: 120 }
→ Múltiplos pares de lançamentos, um por recebedor + taxa
SettlementCompleted { seller: seller_a, amount: 400, method: PIX }
→ DÉBITO: escrow_seller_a R$ 400 / CRÉDITO: clearing_liquidacao R$ 400
Porque eventos são imutáveis e ordenados, o ledger pode ser reconstruído do zero re-executando todos os eventos. Fazemos isso periodicamente como verificação: reconstruímos o ledger inteiro a partir do event store e comparamos com o ledger ao vivo. Se batem (sempre bateram), sabemos que o ledger está correto.
Essa é a garantia de auditoria definitiva: o ledger não é apenas auditável — é reconstruível.
Reconciliação em tempo real: capturando discrepâncias em milissegundos
Reconciliação tradicional é um processo batch. No fim do dia, alguém (ou algum job) compara registros internos contra extratos bancários externos. Discrepâncias encontradas hoje podem se relacionar com transações de 3 dias atrás. Boa sorte investigando uma discrepância de 3 dias quando você processa 100.000 transações por dia.
Nosso engine de reconciliação opera em tempo real:
Matching por eventos
Quando um evento externo chega (confirmação PIX do SPI, confirmação TED da CIP, pagamento de boleto da câmara de compensação), o engine de reconciliação imediatamente pareia com o lançamento interno correspondente.
Match encontrado? Status atualizado para RECONCILED. Sem match? Alerta dispara em segundos.
Verificação contínua de balanço
A cada 60 segundos, o engine verifica a equação contábil fundamental em todas as contas:
SUM(todos_debitos) == SUM(todos_creditos)
Se essa equação não se mantém — mesmo por R$ 0,01 — um alerta dispara imediatamente. Capturamos discrepâncias no minuto em que ocorrem, não dias depois.
Reconciliação cross-system
O engine reconcilia contra múltiplos sistemas externos simultaneamente:
- Liquidações PIX via SPI (confirmações pacs.002)
- Liquidações TED via CIP
- Pagamentos de boleto via arquivos da câmara de compensação
- Liquidações de bandeiras de cartão via arquivos Visa/Mastercard
- Saldos de contas bancárias via extratos camt.053
Cada sistema externo é um stream de reconciliação. Todos os streams são processados em tempo real. O dashboard de reconciliação mostra uma visão ao vivo de itens pareados vs. não pareados em todos os sistemas.
Compliance COSIF: contabilidade que reguladores entendem
COSIF (Plano Contábil das Instituições do Sistema Financeiro Nacional) é o plano de contas obrigatório do BACEN para todas as instituições financeiras no Brasil. Cada saldo, cada transação, cada relatório deve mapear para códigos de conta COSIF.
A maioria das fintechs trata COSIF como camada de tradução — suas contas internas usam códigos arbitrários, e um processo separado mapeia para COSIF no reporting. Isso cria os mesmos problemas de camada de tradução que discutimos no post de ISO 20022: perda de dados, erros de mapeamento e carga de manutenção.
O plano de contas da Revenu é nativamente alinhado ao COSIF. Cada conta interna mapeia 1:1 para um código COSIF. Quando precisamos gerar um balanço COSIF-compliant, consultamos o ledger diretamente — sem tradução, sem mapeamento, sem aproximação.
Isso significa:
- Documentos CADOC são gerados a partir de dados ao vivo do ledger
- Balanços regulatórios são sempre consistentes com dados operacionais
- Solicitações de auditoria são respondidas consultando o ledger, não reconstruindo a partir de sistemas díspares
Performance: como lidamos com 50.000 lançamentos por segundo
Um ledger de partida dobrada ingênuo serializa todas as operações através de um único lock. Isso limita throughput a ~1.000 TPS. Para um processador de pagamentos lidando com PIX (que liquida em < 2 segundos), não é suficiente.
Paralelismo em nível de conta
Transações que tocam contas diferentes podem executar em paralelo. Apenas transações tocando a mesma conta são serializadas. Como nosso espaço de contas é grande (milhões de contas), o paralelismo é alto.
Batch posting
Múltiplos pares de lançamentos da mesma operação de negócio são agrupados em uma única transação de banco de dados. Um split payment com 10 recebedores gera 10 pares de lançamentos — todos escritos em um batch atômico, não 10 inserts sequenciais.
Escritas append-only
Entradas imutáveis de ledger são append-only. Sem row locks, sem ciclos read-modify-write. Apenas INSERT. Esse é o padrão de escrita mais rápido para qualquer banco de dados.
Read replicas para consultas
Consultas de saldo e relatórios leem de replicas dedicadas, nunca do caminho de escrita. Isso elimina contenção de leitura/escrita. Views de saldo são eventualmente consistentes com lag de < 100ms.
Os números
- 50.000 lançamentos/segundo throughput sustentado
- < 5ms por par de lançamentos (p99)
- < 100ms tempo de resposta de consulta de saldo
- Zero conflitos de escrita — arquitetura append-only elimina contenção de locks
Os números de produção
Após 18 meses e R$ 8 bilhões em volume processado:
- Discrepância de saldo: R$ 0,00 — o ledger nunca ficou fora de balanço
- 11 engines especializados trabalhando em concerto
- 50.000 lançamentos/segundo throughput sustentado
- < 5ms latência de lançamento (p99)
- 100% alinhamento COSIF — sem camadas de tradução para reporting regulatório
- Reconciliação em tempo real — discrepâncias capturadas em < 60 segundos
- 0 correções manuais — todas as correções são reversão automática + re-lançamento
- Arquivo de 10 anos — histórico completo de ledger, recuperável em < 48 horas
- Verificação de reconstrução de ledger: reconstrução mensal completa a partir do event store — 100% de match toda vez
Por que isso importa
Um ledger não é uma feature. É a fundação de todo o resto: pagamentos, compliance, reporting, reconciliação, auditoria. Se o ledger está errado, tudo construído em cima dele está errado.
A maioria das fintechs descobre que seu ledger está errado quando não consegue fechar os livros no fim do mês. Ou quando um auditor encontra uma discrepância que não conseguem explicar. Ou quando um relatório regulatório não bate com os dados operacionais. A essa altura, o custo de consertar é enorme — às vezes existencial.
Nós desenhamos nosso ledger para tornar esses cenários impossíveis. Não improváveis — impossíveis. Conservação de valor é imposta no nível da transação. Imutabilidade é imposta no nível do banco de dados. Reconciliação roda continuamente. A equação contábil é verificada a cada 60 segundos.
O resultado: um ledger em que você pode confiar. Não porque alguém diz que está correto, mas porque a arquitetura torna matematicamente impossível que esteja errado.
Isso é o que a contabilidade de partida dobrada foi projetada para fazer 700 anos atrás. Nós só implementamos direito.