São 23h47 de um sábado. O cliente abre o app, faz um Pix de R$ 380 para o irmão, a tela gira, e aparece a mensagem mais odiada do sistema financeiro brasileiro: “Estamos processando sua transação.”
O saldo dele já foi debitado. O irmão não recebeu nada. O suporte não sabe dizer se o dinheiro saiu. E o time de plantão abre o dashboard e encontra uma linha no banco de dados com status = 'PENDENTE' e um updated_at de quarenta minutos atrás.
A pergunta que todo mundo faz nesse momento é a errada. A pergunta errada é “qual foi o bug?”. A pergunta certa é: “o que o meu sistema sabe, o que o SPI sabe, e por que essas duas coisas divergiram?”
Porque na esmagadora maioria das vezes não há bug nenhum. Há um sistema distribuído funcionando exatamente como especificado, e um código local que foi escrito assumindo garantias que o arranjo do Pix nunca ofereceu.
Este artigo é sobre essas garantias — as que existem, as que não existem, e o que o Banco Central exige que você faça no meio do caminho.
Pense comigo: se o seu código não respeita isso…
Se o seu sistema representa uma transação Pix como uma linha com um campo status que sofre UPDATE, você já perdeu antes de começar. Não porque seja feio, mas porque você acaba de destruir a única coisa que te salva numa auditoria ou num incidente: a capacidade de responder quando e por causa de qual mensagem aquela transação mudou de estado.
Um campo status é uma fotografia. O que o Pix exige de você é um filme. E filme se guarda em log de transições, não em coluna sobrescrita.
Assincronismo não é escolha de arquitetura — é imposição do arranjo
Existe um mal-entendido confortável que circula em muito time: o de que o Pix “é assíncrono porque o banco escolheu processar em fila”. Não é. O assincronismo está na especificação, não na sua implementação.
O fluxo interbancário funciona assim: o PSP do pagador envia uma ordem de pagamento ao SPI. Em algum momento — que pode ser em 200 milissegundos ou pode não acontecer nunca — chega de volta uma outra mensagem, independente, com o veredito. Não existe um HTTP 200 que signifique “liquidado”. Não existe BEGIN TRANSACTION … COMMIT atravessando três instituições e a rede do Banco Central. Não existe rollback.
Vale a analogia do correio registrado. Você não liga para o destinatário e espera na linha até ele confirmar que recebeu. Você posta a carta, recebe um comprovante de postagem, e dias depois chega um aviso de recebimento — ou não chega. O comprovante de postagem prova que você postou. Não prova que entregou. Confundir os dois é a origem da maior parte dos incidentes graves de Pix.
Há ainda um detalhe do catálogo que costuma pegar times desprevenidos: a granularidade da mensagem não é necessariamente a granularidade da transação. Uma mesma mensagem de ordem de pagamento pode carregar múltiplas operações agregadas, e o mesmo vale para a mensagem de resposta. Qualquer modelo mental que assuma “uma mensagem = um pagamento” vai quebrar no primeiro cenário de agregação.
Pense comigo: se o seu código não respeita isso…
Se você escreveu um service que chama o gateway do SPI e faz await na resposta esperando o resultado final da liquidação, o seu código tem uma premissa embutida que a rede não honra. O que vai acontecer, inevitavelmente:
a thread vai estourar timeout antes do arranjo;
o seu
catchvai capturar uma exceção de rede e traduzir como “falhou”;o seu código vai estornar o débito do cliente;
e três segundos depois o SPI vai confirmar a liquidação daquela transação, que realmente aconteceu.
Resultado: o cliente recebeu o dinheiro de volta e o beneficiário recebeu o valor. Você acabou de criar dinheiro. Isso não é um bug de borda — é a consequência lógica e previsível de tratar ausência de resposta como negativa de resposta.
Regra que vale gravar: em sistema de pagamento, timeout não é resultado. Timeout é ausência de informação, e ausência de informação exige consulta, não decisão.
As três pacs: o vocabulário completo do Pix
Aqui está o fato que quase todo conteúdo sobre Pix apresenta de forma difusa, e que na verdade é enxuto a ponto de caber em um parágrafo.
O SPI usa o padrão internacional ISO 20022, e a família pacs — de Payments Clearing and Settlement — tem exatamente três mensagens no arranjo do Pix. Três. O próprio SPI expõe um endpoint de catálogo que lista as versões de mensagem vigentes, e na família pacs aparecem pacs.002, pacs.004 e pacs.008. Nada além disso.
pacs.008 — a intenção
FIToFICustomerCreditTransfer. É a ordem de pagamento: o PSP do pagador declarando ao SPI que quer transferir um valor de uma conta para outra. Carrega o identificador fim a fim da transação, os dados de pagador e recebedor, o valor, a finalidade, e — detalhe operacionalmente importante — um campo de prioridade de liquidação.
O que ela é: uma declaração de intenção assinada digitalmente.
O que ela não é: prova de que o dinheiro saiu.
Um ponto que rende decisão de arquitetura: a prioridade preenchida nessa mensagem determina por qual canal de transmissão a operação vai trafegar, e essa escolha se propaga para todas as mensagens seguintes da mesma operação. Ou seja, a decisão tomada no momento da iniciação amarra o comportamento do ciclo inteiro. Não é um parâmetro cosmético.
pacs.002 — o veredito
FIToFIPaymentStatusReport. É a mensagem que efetivamente muda o estado do mundo. Ela referencia a mensagem original e carrega o status: aceito e liquidado, ou rejeitado com um código de motivo padronizado.
É a operária da rede, e por dois motivos que costumam confundir:
Ela aparece em mais de um fluxo. Responde à ordem de pagamento, mas também responde à mensagem de devolução. Se o seu handler assume que toda
pacs.002se refere a um pagamento original, ele vai processar errado o retorno de uma devolução.Os códigos de rejeição não são todos iguais. O padrão ISO define motivos de naturezas muito diferentes: conta encerrada, conta bloqueada, transação proibida para aquele tipo de conta, liquidação abortada por timeout, transação interrompida por timeout no agente do credor, agente do credor fora do ar.
Colocar tudo isso num único if (status != OK) marcarComoFalha() é jogar fora a informação mais cara que o arranjo te entrega. “Conta encerrada” é erro de negócio definitivo — você informa o cliente e encerra. “Timeout no agente do credor” é erro de infraestrutura de terceiro — pode ser retentável, pode não ser, e a decisão depende de você saber se houve liquidação. São dois mundos.
pacs.004 — o arrependimento
PaymentReturn. É a devolução. Segundo o Catálogo de Serviços do SFN, quando o usuário recebedor identifica que a transferência foi feita de forma indevida e quer devolver o valor ao pagador, entram em cena a pacs.004 e a pacs.002 correspondente.
E aqui está a assimetria mais importante do arranjo inteiro, aquela que sustenta a tese deste artigo:
Não existe mensagem de cancelamento no Pix. Você não desfaz um pagamento. Você faz outro, no sentido inverso, com vínculo ao original.
Em vocabulário de engenharia: o Pix não tem rollback, tem evento compensatório. Quem já trabalhou com Event Sourcing reconhece o padrão na hora — você não apaga o evento errado do log, você emite um evento que o compensa, e ambos ficam registrados para sempre. O Bacen desenhou o arranjo assim de propósito: em sistema financeiro, apagar história é pior do que registrar erro.
Há ainda uma restrição operacional específica dessa mensagem. A devolução sempre trafega pelo canal primário de transmissão, mesmo tendo campo de prioridade — e as respostas decorrentes dela também. Isto é, mesmo que a operação original tenha ido pelo canal secundário, a devolução não vai. Se a sua camada de transporte roteia mensagem por prioridade de forma genérica, ela vai violar essa regra sem avisar ninguém.
As vizinhas que ninguém apresenta
Falar só das três pacs dá uma visão incompleta do ciclo. Ao redor delas existe um conjunto que costuma ser implementado às pressas, e é exatamente onde moram os incidentes silenciosos:
admi.002— erro não terminativo. Comunicação de erro que não encerra a transação. Se o seu código trata isso como rejeição, você marca como morta uma transação que ainda está viva. Analogia: o garçom voltou à mesa dizendo que não entendeu o pedido. Isso não significa que a cozinha recusou o prato.admi.004— avisos sobre a operação do SPI. É a mensagem que fala do sistema, não da sua transação.Família
camt— conciliação e conta. Acamt.060vai do PSP para o SPI como requisição, e a resposta vem emcamt.052,camt.053oucamt.054, conforme a natureza do que se pediu. Há aindacamt.014para avisos de alteração de dados cadastrais de participante ecamt.025para resultado de gravação. Este é o material bruto da sua conciliação — e, como veremos mais pra frente, é também o único lugar de onde você consegue descobrir o que realmente aconteceu com uma transação.Família
REDA— gestão de participantes indiretos.REDA.014eREDA.031do PSP para o SPI,REDA.016eREDA.017no sentido inverso. Se você opera como liquidante ou depende de um, esse é o vocabulário que descreve a sua relação com ele.pibr.001epibr.002— teste de ECO. Mensagens proprietárias brasileiras, o heartbeat da sua conexão com a rede. Elas respondem à pergunta “o canal está vivo?”, que é diferente de “minha transação foi processada?”.Família
LPI— mensagens proprietárias de movimentação de saldo da Conta PI.
E onde é que a pacs não aparece
Ponto de confusão frequente, principalmente em quem entrou no Pix pela porta de um BaaS: as pacs só existem entre o participante e o SPI.
O DICT — o diretório de chaves — é REST/JSON. A API Pix que você usa para gerar cobrança, consultar um pix recebido ou receber webhook também é REST/JSON. Não há uma linha de ISO 20022 ali. Quem integra com um provedor nunca vê uma pacs na vida: vê o JSON que alguém traduziu por ele.
Isso significa que existe, obrigatoriamente, uma camada de tradução entre o vocabulário do arranjo e o vocabulário do seu domínio. Se essa camada não for explícita e isolada — uma Anti-Corruption Layer de manual —, ela existe assim mesmo, só que espalhada por dezenove classes do seu core.
Pense comigo: se o seu código não respeita isso…
Suponha que o seu handler de mensagens tenha esta forma, que é a forma mais comum que existe:
if (mensagem.status == "ACSC") { confirmar(); }
else { falhar(); }Esse else está engolindo, no mínimo:
rejeições de negócio definitivas (conta encerrada) — que deveriam informar o cliente com mensagem específica;
rejeições de infraestrutura de terceiro (agente do credor fora do ar) — que podem ser reapresentáveis;
abortos por timeout de liquidação — que exigem consulta, não conclusão;
erros não terminativos que nem sequer são rejeição.
Quatro tratamentos radicalmente diferentes, achatados em uma palavra. Toda vez que alguém pergunta “por que nosso Pix tem tanto estorno indevido?”, a resposta costuma estar nesse else.
A máquina de estados que o arranjo exige de você
Agora podemos montar o modelo. Do lado do PSP pagador, o ciclo de vida mínimo honesto de uma transação é:
INICIADA
→ VALIDADA
→ DEBITADA / SALDO_RESERVADO
→ ENVIADA_AO_SPI
→ { LIQUIDADA | REJEITADA | INDETERMINADA }
→ [ DEVOLVIDA ]
Três observações que transformam isso de diagrama bonito em decisão de engenharia.
Os estados finais não são dois, são três. Todo mundo modela sucesso e falha. Quase ninguém modela o terceiro, que é o único que realmente dói. Voltaremos a ele no próximo tópico.
LIQUIDADAnão é estado terminal. Uma transação liquidada pode receber devolução depois — por erro operacional, por acordo entre as partes ou por mecanismo regulatório. Quem modelou liquidação como estado absorvente vai precisar reabrir o modelo, e reabrir máquina de estados em produção é caro.Terceira: transições precisam ser fatos registrados, não mutações. Cada mudança de estado deve guardar: o estado anterior, o estado novo, o identificador da mensagem que causou a mudança, o timestamp do arranjo e o timestamp local. Sem isso você não consegue responder à única pergunta que importa numa apuração de incidente: “em que instante nós soubemos disso, e por quê?”
A analogia que costuma destravar a conversa com o time de produto é a do livro-razão contábil. Contador nunca apaga lançamento errado — ele faz um lançamento de estorno. O livro cresce, nunca encolhe, e o saldo é sempre derivado da soma dos lançamentos. Se contabilidade descobriu isso há seiscentos anos e o Bacen desenhou o Pix sobre esse mesmo princípio, talvez o seu UPDATE transacao SET status = 'OK' mereça uma segunda olhada.
Pense comigo:
Imagine o cenário real: chega uma pacs.002 de confirmação e, dois segundos depois, chega uma segunda cópia da mesma mensagem — o que, como veremos mais pra frente, é comportamento previsto do canal de comunicação, não anomalia.
Com um campo status mutável, as duas execuções são indistinguíveis. Com máquina de estados e transições registradas, a segunda mensagem encontra uma transição já existente para aquele identificador, é reconhecida como reentrega e descartada. Não é elegância arquitetural — é a diferença entre creditar o cliente uma vez ou duas.
E o corolário, que é o coração do tópico seguinte: a sua máquina de estados precisa ter uma transição legítima para “eu não sei”. Se o único caminho que sai de ENVIADA_AO_SPI vai para sucesso ou falha, você forçou o sistema a mentir quando ele não sabe. E sistema que mente sobre dinheiro cria prejuízo contábil.
O estado indeterminado — o que fazer quando a resposta não vem
Este é o tópico que separa quem já operou Pix em produção de quem leu sobre Pix.
O SPI opera controle de timeout em dois pontos distintos do fluxo. O primeiro cobre o intervalo entre o PSP pagador receber a ordem do cliente e efetivamente enviá-la ao SPI — e é o próprio PSP quem declara esse valor na mensagem. O segundo cobre o intervalo entre o SPI receber a ordem e a liquidação efetiva, o que inclui a validação e o retorno por parte do PSP recebedor. Quando o controle foi ativado, em 2020, esses valores foram estabelecidos provisoriamente em cinco e dez minutos, respectivamente. Os números vigentes devem ser conferidos no catálogo atual antes de qualquer implementação — o que não muda é a existência dos dois controles e o que eles significam para você.
Repare no escopo do segundo: ele engloba o comportamento de outra instituição. O seu relógio de incerteza depende da disponibilidade de um terceiro sobre o qual você não tem controle, visibilidade nem SLA contratual. Essa é a definição operacional de dependência distribuída.
A consequência é esta: existe um intervalo de tempo real, mensurável, em que a sua transação já saiu do seu domínio e você não sabe o desfecho. Não é falha. É o estado normal do sistema entre dois pontos.
A idempotência não é convenção sua — é norma, e tem janela
O Catálogo de Serviços do SFN estabelece que tanto o ciclo básico de transferência quanto o de devolução operam sob o princípio da idempotência, com janela de 24 horas.
Leia de novo, porque isso muda a política de retentativa do seu sistema inteiro. Reapresentar a mesma operação, com o mesmo identificador, dentro da janela, é comportamento previsto pela especificação — não é gambiarra e não gera duplicidade. Fora da janela, a mesma operação passa a ser uma transação nova, com efeito financeiro novo.
O que essa regra implica na prática:
o identificador fim a fim é a sua chave de idempotência. Não invente outra, não use o seu ID interno como âncora com o arranjo;
ele precisa ser gerado antes do envio e persistido junto com a intenção. Gerar identificador no momento do envio significa que uma queda entre a persistência e o envio te deixa sem chave para consultar;
a sua política de retentativa precisa conhecer o relógio de 24 horas. Um job de reprocessamento que varre transações antigas sem essa noção é uma máquina de duplicar pagamento com pinta de rotina de manutenção.
Reentrega é comportamento especificado, não anomalia
O guia de implementação do canal de comunicação é explícito quanto a um ponto que muda a forma de escrever o consumidor: a ausência de confirmação de leitura no tempo adequado pode fazer com que a mesma mensagem seja disponibilizada novamente, onerando tanto os sistemas do Banco Central quanto os do próprio participante. O guia também trata do ritmo de leitura do endpoint de recepção, com tempo máximo recomendado na ordem de 200 milissegundos no canal primário e intervalo maior no canal secundário.
Traduzindo para o vocabulário que usamos todo dia: a entrega é at-least-once. Você vai receber mensagem repetida. Não ocasionalmente, não em cenário de desastre — no funcionamento normal, sempre que a confirmação atrasar.
Quem trabalha com Kafka, SQS ou qualquer broker moderno já sabe onde isso vai dar: exactly-once no transporte é ficção, e a única defesa real é processamento idempotente no consumidor. A diferença é que aqui o payload duplicado não é um evento de analytics. É trezentos e oitenta reais.
A política que separa os times maduros dos demais
Diante de uma transação indeterminada, existem três condutas possíveis. Duas estão erradas:
Errado — concluir como falha e estornar o cliente. Se a liquidação ocorreu, você criou dinheiro.
Errado — reapresentar cegamente sem controle de janela. Fora das 24 horas, você paga duas vezes.
Certo — consultar. Manter a transação em estado indeterminado explícito, com o cliente informado com honestidade, e resolver por reconciliação contra a fonte da verdade — que não é o seu banco de dados.
“Consultar” onde, exatamente?
Essa é a pergunta que separa o conselho bonito da engenharia de verdade. Falar “consulte o SPI” é fácil até alguém pedir o endpoint. Então vamos ao que existe de fato.
Primeiro fato, e ele reorganiza tudo: não existe uma consulta de situação de transação. A família pacs tem três mensagens — ordem, veredito e devolução — e nenhuma delas serve para perguntar “qual o status do meu identificador fim a fim?”. Não há o equivalente a um GET /transacao/{e2e}/status. Quem procura por isso no catálogo não vai achar, e isso não é omissão: é decorrência do desenho.
O que existe é a família camt, e o Catálogo de Serviços do SFN é preciso quanto ao papel dela: são mensagens que permitem consultas diversas relacionadas à Conta PI. A camt.060 é a requisição, enviada do PSP para o SPI, e pode ser respondida com camt.052, camt.053 ou camt.054, a depender da natureza do que foi pedido. O roteiro de participação direta no SPI detalha os três caminhos que o participante precisa demonstrar que sabe percorrer:
camt.060→camt.053: saldo de fechamento de um dia anterior;camt.060→camt.054: detalhes de um lançamento específico;camt.060→camt.052: indicação do arquivo que contém a relação de lançamentos, seguida do download desse arquivo.
A inversão mental que isso exige
Você não pergunta ao SPI “essa transação liquidou?”. Você pergunta “existe lançamento correspondente na minha Conta PI?”.
A diferença não é semântica, é de modelo. No Pix, liquidação é um fato contábil, e o status da sua transação é uma propriedade derivada desse fato — não o contrário. A mensagem de veredito é a notificação do fato; o lançamento na Conta PI é o fato. Quando as duas coisas divergem, o lançamento ganha, sempre.
A analogia do dia a dia: quando você não tem certeza se uma compra passou, você não liga para a loja perguntando o status do pedido. Você abre o extrato e olha se saiu do seu saldo. O extrato não é a opinião de ninguém sobre o que aconteceu — é o que aconteceu.
E aqui vem a parte que quebra a arquitetura ingênua
A consulta também é assíncrona. A camt.060 é uma mensagem postada no canal, não uma chamada REST síncrona. Você envia a pergunta e a resposta chega depois, por outra mensagem, pelo mesmo endpoint de recepção que já entrega as suas pacs. E no caminho da relação de lançamentos há ainda um passo adicional de download de arquivo.
A consequência é dura e vale escrever em negrito: não existe nenhuma forma de resolver uma transação indeterminada dentro do ciclo de requisição do cliente. Nenhuma. Se o seu desenho previa “se der timeout, consulta o SPI e responde ao app”, esse desenho não é implementável. A consulta é um segundo fluxo assíncrono, e o cliente vai precisar ser notificado depois.
Os quatro recursos, em ordem de uso
Esperar a reentrega. Antes de qualquer coisa, lembre que o canal reentrega mensagem não confirmada. Uma fatia relevante dos “sumiços” é, na verdade, consumidor que confirmou a leitura e falhou ao processar depois — o arranjo cumpriu a parte dele, você é que perdeu a mensagem. Confirmar leitura antes de persistir o fato é um dos bugs mais caros e mais comuns dessa integração.
Consultar o lançamento via
camt.060, com resposta emcamt.054. É a consulta cirúrgica, para uma transação específica.Varrer a relação de lançamentos via
camt.060→camt.052→ download do arquivo. É a varredura em lote, base da conciliação transacional. É aqui que você descobre transações que existem no SPI e não existem na sua base — as órfãs, que a consulta cirúrgica jamais encontraria, porque você não sabe que elas existem para perguntar por elas.O SPB-Web, módulo SPI. Interface humana, com autenticação de segundo fator e consultas tarifadas conforme a tabela de serviços do Sisbacen. Não serve ao seu microsserviço — serve ao analista de plantão às 3h da manhã. Mas precisa estar no runbook, com acesso provisionado antes do incidente, e não durante.
O que isso significa em desenho de serviço
Junte as peças e o formato aparece sozinho: a reconciliação não é um retry dentro do serviço de pagamento. É um serviço próprio, com responsabilidade única, que:
consome transações que entraram em estado indeterminado e envelheceram além do limite do arranjo;
emite
camt.060respeitando o custo e o volume dessas consultas — consulta não é grátis, nem em tarifa nem em capacidade;correlaciona a resposta assíncrona de volta ao identificador fim a fim original — o que exige guardar a correlação entre a sua requisição e a transação investigada, porque a resposta vai chegar desacoplada;
emite um evento de resolução que reabre a máquina de estados naquele ponto exato;
e expõe como métrica de negócio a quantidade e a idade das transações não resolvidas.
Quem tenta encaixar isso dentro do serviço que processa pagamento acaba com um componente que faz duas coisas com perfis de latência, criticidade e escala incompatíveis. Separar não é purismo de arquitetura — é a única forma de a reconciliação não competir por recurso com o caminho crítico justamente no pior momento, que é quando as duas coisas acontecem juntas.
Pense comigo: se o seu código não respeita isso…
Há uma regra de dimensionamento que decorre de tudo isso e que quase nenhum time aplica conscientemente: os seus timeouts internos precisam ser menores que os do arranjo, nunca maiores.
Se o seu circuit breaker abre depois do timeout do SPI, ele nunca protege nada — só te informa de um desastre que já aconteceu. Se o timeout do seu cliente HTTP interno é maior que o do arranjo, você segura threads esperando resposta de uma operação que o SPI já abortou. Em horário de pico, isso não degrada um serviço: derruba o pool de conexões e leva junto tudo o que compartilha aquele recurso.
Timeout, aqui, não é configuração de infraestrutura. É decisão de arquitetura derivada de norma.
O outro lado do balcão: onde fica a fronteira do irreversível
Trocamos de cadeira. Agora a mensagem chega até você.
Existe uma pergunta que aparece em toda discussão de arquitetura de Pix recebido, e que eu quero desarmar logo de início, porque ela está mal formulada: “a gente credita o cliente antes ou depois de responder ao SPI?”
Parece um dilema aberto, com dois lados defensáveis. Não é. O arranjo já respondeu essa pergunta por você, e quem trata isso como escolha livre normalmente está prestes a descobrir o motivo da forma cara.
Por que a ordem não é negociável
O PSP recebedor tem duas obrigações simultâneas: disponibilizar os recursos ao usuário e responder ao arranjo. A segunda é medida. O Manual de Tempos do Pix estabelece indicadores apurados por percentil, com apuração obrigatória por todos os participantes — diretos e indiretos —, e esses indicadores compõem o Índice Geral de ANS. A ordem de grandeza dos tempos é de poucos segundos: só para ilustrar o padrão de rigor, no ciclo de autorização do Pix Automático há indicador apurado a percentil de 95% com limite de três segundos.
Some a isso o índice mínimo de disponibilidade de 99,9% previsto no arcabouço da Resolução BCB nº 195, e o quadro fica claro: o seu compromisso com a rede é medido em segundos, na cauda da distribuição, e auditado.
Agora responda honestamente: o seu core bancário consegue creditar uma conta dentro desse orçamento de tempo, no percentil 99, às 20h de uma sexta-feira, durante um pico de volume, com o motor antifraude no caminho?
Se a resposta é “quase sempre”, você acabou de amarrar um compromisso regulatório medido por percentil à parte mais lenta e mais compartilhada da sua infraestrutura. E percentil não perdoa “quase sempre” — percentil é exatamente a métrica desenhada para capturar o “quase”.
O que o mercado convergiu a fazer
O padrão dominante entre bancos e provedores de BaaS não é um meio-termo diplomático. É este, e é bem definido:
Validação síncrona, rápida e determinística. A conta existe? Está apta a receber? Está bloqueada? O tipo de conta aceita aquela operação? As regras de antifraude que precisam ser síncronas passaram? É daqui que saem as rejeições legítimas, com o código de motivo específico que discutimos no anteriormente.
Aceite ao arranjo assim que a validação passa. Esse é o ponto de não retorno.
Crédito ao cliente como consequência garantida, disparado por mecanismo durável — outbox, fila com garantia de entrega, o que a sua stack oferecer — e não dentro do mesmo bloco transacional que processa a mensagem do arranjo.
Repare no que isso significa: entre o aceite e o crédito efetivo existe uma janela, curta mas real, em que a rede já considera o dinheiro entregue e o seu cliente ainda não o vê. Essa janela não é um defeito da implementação. É o preço estrutural de cumprir um SLA externo com um recurso interno mais lento — e todo participante do Pix paga esse preço, saiba ele disso ou não.
A pergunta certa, então, é outra
Se a ordem já está definida, o que sobra para você decidir? Sobra a única coisa que ainda importa: o que precisa estar verificado antes do aceite.
Vamos com um exemplo bem simples. Duas coisas que o seu sistema faz quando um Pix chega:
verificar se a conta do destinatário existe;
enviar a notificação no celular dele.
A primeira pode dizer “não”. Se a conta não existe, a operação não acontece. A segunda nunca diz “não” — ela só executa. Se a notificação falhar, o dinheiro entrou do mesmo jeito.
É essa a diferença que organiza o fluxo inteiro:
O que pode recusar vem antes do aceite. O que apenas executa vem depois.
O motivo é direto: aceitar é prometer. No instante em que você responde “aceito” ao arranjo, você assumiu uma dívida com o cliente. Tudo que você deixou para depois desse momento, você já prometeu — e não tem mais como voltar atrás.
Colocar uma regra que pode recusar depois do aceite cria uma situação sem saída. Você já disse sim para a rede e vai precisar dizer não para o cliente. A única forma de sair disso é devolver o dinheiro.
Na prática: o que fica de cada lado
Antes do aceite ficam as verificações que podem recusar. Elas precisam ser rápidas e ter resposta certa:
a conta existe e está ativa;
a conta não está bloqueada — por decisão judicial, administrativa ou cautelar;
o tipo de conta aceita aquela operação;
a operação não estoura limite cadastral ou regulatório do titular;
as checagens de conformidade que não podem esperar, como listas restritivas.
Repare que os códigos de rejeição do padrão separam “conta encerrada” de “conta bloqueada”. Não é preciosismo: são situações diferentes, e o cliente do outro lado precisa ouvir coisas diferentes.
Depois do aceite fica tudo que é consequência do dinheiro que já entrou:
notificação ao usuário;
categorização do lançamento;
atualização de extrato e das telas de saldo;
contabilidade analítica;
programa de pontos, atualização de limites, esteiras de análise.
Nenhuma dessas etapas pode recusar a operação. Se alguma delas puder, ela está no lado errado — e o seu sistema tem um problema esperando o dia certo para aparecer.
O teste de uma pergunta
Para cada regra do seu fluxo, pergunte:
“Se essa verificação falhar depois que eu já aceitei, o que eu faço?”
Se a única resposta for “devolver o dinheiro”, ela precisa ser síncrona. Ponto.
A devolução existe e é legítima. Mas ela é um mecanismo de exceção. Usar devolução como rotina, para compensar uma decisão de arquitetura, é transformar exceção em processo — e isso sempre cobra a conta quando o volume cresce.
É exatamente por isso que o antifraude síncrono é o assunto mais espinhoso desse fluxo. Ele é, por definição, uma regra que pode recusar. Ou ele cabe no tempo disponível, ou precisa ser redesenhado para atuar em outro momento, com outro mecanismo. Não existe uma terceira opção confortável.
O espelho do outro lado: o que fazer com o saldo de quem paga
Agora gire a cadeira de volta. A mesma lógica existe do lado de quem envia — só que ali tem uma complicação a mais: o dinheiro está saindo da conta de alguém antes de existir qualquer certeza.
Voltemos ao João, que quer mandar R$ 380. Ele tem saldo, e você já validou tudo. A pergunta é: o que você faz com esses R$ 380 no momento em que envia a ordem?
Existem três respostas possíveis, e duas delas dão errado.
Opção 1: não mexer no saldo agora
Você envia a ordem e deixa o saldo do João intacto até a confirmação chegar.
Problema: nos próximos segundos, nada impede que ele pague uma conta, faça outro Pix ou passe o cartão. Quando a confirmação chegar, o dinheiro pode não estar mais lá. Você mandou ao arranjo uma ordem sem lastro.
Opção 2: debitar de vez
Você tira os R$ 380 da conta, como pagamento efetuado, e envia.
Problema: se a operação for recusada, você precisa fazer um estorno. Estorno é um novo lançamento contábil, com rastro, com possibilidade de falha e com custo operacional. Agora multiplique isso pelo número de rejeições legítimas que uma instituição recebe por dia.
Opção 3: reservar o saldo — e é essa que funciona
Os R$ 380 saem da disponibilidade do João, mas ainda não viram pagamento. Ficam numa posição intermediária: ele não pode gastar, e você não contabilizou nada.
Depois, uma de duas coisas acontece:
chegou a confirmação → a reserva vira pagamento efetivado;
chegou a recusa → a reserva é liberada e o saldo volta a ficar disponível.
Simples assim. E o ponto elegante: na recusa não existe estorno. Não há lançamento de compensação, não há dinheiro que circulou e voltou. Contabilmente, aquele valor nunca chegou a sair da conta.
Três cuidados com a reserva
1. Reservar e registrar precisam acontecer juntos. Mesma transação de banco, mesma unidade de trabalho. Se você reserva e não registra, cria saldo preso sem dono. Se você registra e não reserva, envia ordem sem lastro. Os dois acontecem em produção; o segundo custa dinheiro.
2. A reserva vem antes do envio. A ordem correta é: validar, reservar, gravar a intenção com o identificador fim a fim, e só então enviar. Qualquer inversão abre uma janela em que o arranjo conhece uma operação que o seu sistema ainda não conhece.
3. Toda reserva tem dono e idade. Ela pertence a uma transação identificável e tem um relógio rodando. Reserva órfã, sem transação correspondente, é dinheiro do cliente preso por um bug silencioso. É um dos achados mais constrangedores que uma conciliação pode produzir.
Quando a recusa chega, a reserva se paga
A instituição do outro lado respondeu: conta encerrada, conta bloqueada, tipo de conta incompatível, agente indisponível. O que você faz?
Libera a reserva. Só isso.
E note uma coisa: a recusa é o melhor tipo de falha que existe. É informação clara, chegando em tempo, permitindo uma decisão certa. É o cenário que você quer.
Aqui aquela classificação de códigos de rejeição mostra o valor. “Essa conta não existe mais, confira os dados” é uma mensagem que o cliente consegue agir. “Erro ao processar” não é.
E quando não chega resposta nenhuma?
Este é o momento em que a reserva deixa de ser boa prática e vira proteção contra prejuízo.
Voltamos ao estado indeterminado. A tentação natural é liberar a reserva, afinal “não deu certo”. Não libere.
Enquanto você não souber se houve liquidação, aquele valor precisa continuar indisponível. A conta é simples:
cliente com saldo bloqueado por alguns minutos → uma reclamação;
cliente com saldo liberado numa transação que liquidou → prejuízo direto, sem contrapartida.
Daí sai uma regra que vale levar escrita para o refinamento:
Reserva só é liberada por recusa explícita ou por reconciliação conclusiva. Nunca por timeout. Nunca por decisão local. Nunca por rotina de limpeza.
Aliás, “job que limpa reservas antigas” é uma das rotinas mais perigosas que existem em sistema de pagamento. Ela parece higiene. É vazamento.
O saldo que quase ninguém verifica
Um último detalhe, que pega até gente experiente: existem dois saldos nessa história, não um.
Um é o saldo do João, no seu core bancário. O outro é o saldo da sua instituição na conta que ela mantém no Banco Central — e é de lá que a liquidação sai de fato.
São sistemas diferentes, áreas diferentes, controles diferentes. O João pode ter saldo de sobra e a operação ser recusada assim mesmo, porque o dinheiro que faltou não era o dele.
Esse assunto é grande o suficiente para merecer um texto próprio, e é um dos que ficaram para a sequência. Por ora, guarde a consequência prática: quando o seu Pix começar a recusar em massa sem que nada tenha mudado no seu código, esse é o segundo lugar para olhar.
E no BaaS, a assimetria é ainda mais forte
Vale um parágrafo específico, porque é onde mora o mal-entendido mais comum do mercado.
Quando você é cliente de um BaaS, o participante do arranjo é o provedor, não você. Quem valida, quem aceita e quem responde ao SPI é ele. O crédito na conta do seu cliente final acontece depois, por outro trilho, sob a sua responsabilidade contratual.
Consequência prática que derruba fintech: o webhook de Pix recebido não é uma solicitação de autorização. É a notificação de um fato já consumado por outra instituição. Não existe “recusar no webhook”. Times que desenham o fluxo assumindo que podem barrar um recebimento naquele ponto descobrem, geralmente em produção, que a decisão foi tomada em uma camada à qual eles não têm acesso.
Isso não é limitação do provedor — é o desenho do arranjo. E é mais um caso em que a camada de tradução entre o vocabulário do Pix e o vocabulário do seu domínio esconde uma diferença semântica profunda por trás de um JSON de aparência inofensiva.
Pense comigo: se o seu código não respeita isso…
Cenário concreto, e eu recomendo levá-lo tal e qual para o próximo refinamento:
O aceite foi enviado ao SPI. O crédito foi enfileirado. O core bancário fica indisponível por vinte minutos.
O que o seu sistema faz?
Se a resposta é “a fila segura e reprocessa quando o core voltar” — ótimo, é exatamente para isso que ela existe. Mas confirme: a mensagem foi persistida antes da confirmação de leitura no canal? Ou você confirmou primeiro e perdeu o fato?
Se a resposta é “as retentativas estouram e a mensagem vai para a fila de erro” — você tem um passivo silencioso. Alguém aceitou dinheiro em nome do cliente e o cliente nunca o recebeu. Isso não gera alarme, não gera exceção, não aparece em nenhum dashboard de erro. Só aparece na conciliação.
Se a resposta é “não sei” — essa é a resposta mais comum e a mais honesta, e ela vale um item de backlog hoje.
A pergunta operacional que fecha o bloco: quem, no seu sistema, é o dono da decisão de crédito? Se o crédito acontece dentro do mesmo bloco que processa a mensagem do arranjo, sem separação, você amarrou o seu compromisso com o Bacen à disponibilidade do seu core. Um lock em tabela de saldo, uma indisponibilidade momentânea, um deploy mal cronometrado — e o que era problema interno vira descumprimento de indicador perante o arranjo.
Aceitar é irreversível. Creditar é garantido. São duas afirmações diferentes, e o seu código precisa saber disso.
A verdade mora no extrato, não na sua base
Chegamos à afirmação mais desconfortável do artigo, e a que dá título à tese:
O seu banco de dados não é a fonte da verdade sobre o dinheiro. A Conta PI, no SPI, é.
O seu banco de dados é uma réplica local, eventualmente consistente, de um estado que vive no Banco Central. Toda a engenharia de um participante do Pix é, no fundo, um exercício contínuo de convergência entre essas duas visões.
É por isso que o arranjo oferece mensagens específicas para isso: consulta de saldo da Conta PI e detalhamento de lançamentos via a família camt. Elas não são acessório. São o instrumento pelo qual você descobre o que realmente aconteceu.
Duas conciliações, não uma
Times maduros mantêm duas, e elas falham por motivos diferentes:
Conciliação transacional: cada identificador fim a fim que existe do meu lado existe do lado do SPI, com o mesmo desfecho? Detecta transação órfã, estado divergente, mensagem perdida. É o que resolve o caso do cliente.
Conciliação contábil: o saldo da minha Conta PI bate com o meu razão interno? Detecta erro sistêmico, lançamento duplicado, valor divergente. É o que resolve o problema do balanço.
A primeira pode estar perfeita e a segunda quebrada — basta um lançamento interno com valor errado numa transação que existe corretamente dos dois lados. E a segunda pode fechar com a primeira furada, se dois erros se compensarem. Quem roda só uma delas dorme tranquilo por motivo errado.
Pense comigo: se o seu código não respeita isso…
Se a conciliação no seu sistema é um script que alguém roda quando o financeiro reclama, ela não é conciliação — é arqueologia.
Conciliação em sistema de pagamento instantâneo precisa ser cidadã de primeira classe: rodando continuamente, com métrica de divergência exposta, com alerta quando o número de transações indeterminadas cresce acima da linha de base. A divergência entre o seu estado e o do SPI é, ela própria, um golden signal — e provavelmente o mais importante que o seu produto tem.
O teste ácido: se o SPI e a sua base discordarem agora, em quanto tempo você fica sabendo? Se a resposta for “quando o cliente ligar”, você tem um problema de arquitetura, não de suporte.
O que levar para o refinamento de amanhã
Vamos amarrar o raciocínio.
O Pix não é difícil por causa do certificado digital ou da assinatura. Isso é trabalho, e trabalho conhecido — qualquer time competente resolve em algumas sprints. O Pix é difícil por um motivo bem menos glamouroso e muito mais estrutural:
Ele é um sistema distribuído de altíssima criticidade, com entrega at-least-once, sem transação distribuída, sem rollback, com a fonte da verdade fora do seu domínio e com o estado do mundo chegando até você por mensagens que podem atrasar, duplicar ou não chegar.
Nenhuma dessas propriedades é acidental. Todas estão na especificação, e cada uma tem consequência direta no seu código.
O fio que costura tudo
Vamos revistar o que já conversamos:
Não existe resposta síncrona — logo, o seu modelo precisa representar espera.
Só existem três
pacs, e nenhuma delas cancela nada — logo, o seu modelo precisa representar compensação, não desfazimento.Erro tem naturezas diferentes, incluindo erro que não encerra a transação — logo, o seu modelo precisa representar mais do que sucesso e falha.
A resposta pode não vir, e a consulta também é assíncrona — logo, o seu modelo precisa representar o “eu não sei” como estado legítimo, e resolvê-lo em outro fluxo.
Aceitar e creditar são compromissos distintos — logo, o seu modelo precisa representar a fronteira entre o que é irreversível e o que é apenas garantido.
A verdade mora na Conta PI, não na sua base — logo, o seu modelo precisa representar a possibilidade de estar errado, e um processo contínuo que o corrija.
Um campo status não carrega. Ele não tem onde guardar “não sei”, não tem onde guardar “por qual mensagem”, não tem onde guardar “quando”, e não sobrevive à próxima norma que acrescentar um estado.
E vai acrescentar. Esse é o ponto que fecha o argumento: este é um domínio que evolui por norma externa, em cadência que você não controla. A agenda regulatória dos próximos anos vai continuar inserindo estados no ciclo de vida da sua transação, e a pergunta que decide o custo disso não é “quanto código vou escrever?”, e sim “acrescentar um estado no meu sistema é uma tarefa de meio dia ou um projeto de um trimestre?”.
Máquina de estados explícita, com transições declaradas e registradas, é o que mantém essa resposta em “meio dia”. Dezenove if espalhados por doze classes é o que a transforma em trimestre.
A jornada invisível — e por que alguém vai pedir para vê-la
Existe um argumento a favor da máquina de estados que raramente aparece em discussão técnica, porque ele não é sobre código. É sobre prova.
Pense no que o usuário final enxerga de uma transferência: uma tela dizendo “enviando” e outra dizendo “concluído”. Duas telas. Entre elas, aconteceram oito, dez, doze fatos distintos — validação, reserva de saldo, envio ao arranjo, liquidação, notificação, crédito, confirmação — cada um em um instante diferente, cada um podendo falhar por um motivo diferente.
Essa é a jornada invisível. Ela existe quer você a registre ou não. A única diferença é se ela fica recuperável depois.
Três pessoas vão bater na sua porta pedindo essa linha do tempo
O atendimento, hoje. O cliente ligou perguntando do dinheiro dele. A pergunta do atendente não é “deu erro?” — é “o que eu digo para ele agora?”. E a resposta correta depende inteiramente de onde a transação parou: “já saiu e o outro banco está processando” é uma conversa completamente diferente de “não chegamos a enviar”. Com um campo status = 'PENDENTE', as duas situações são indistinguíveis, e o atendente é obrigado a improvisar. Improviso em conversa sobre dinheiro do cliente é como se produz reclamação em ouvidoria.
O engenheiro, no incidente. Duzentas transações travadas. A pergunta útil não é “quantas falharam?”, é “todas elas pararam no mesmo ponto?”. Se pararam, você tem uma causa comum e uma correção. Se estão espalhadas por estados diferentes, você tem outro problema, provavelmente mais grave. Sem transições registradas, essa distinção — que é a diferença entre resolver em vinte minutos e resolver em seis horas — simplesmente não é obtível.
O auditor, meses depois. E esse é o mais implacável dos três, porque ele não aceita “acho que”.
Auditoria: o ônus da prova é seu
Em sistema financeiro regulado, a assimetria é essa: ausência de registro não é interpretada como ausência de problema, é interpretada contra você. “Não temos esse dado” e “fizemos errado” produzem, na prática, o mesmo desfecho.
E a demanda por essa prova não é hipotética. Ela é rotina do arranjo:
Uma contestação via mecanismo de devolução exige demonstrar o que foi feito e quando — e, com o MED em sua versão atual, com prazos e etapas que são eles próprios medidos.
Os indicadores do Manual de Tempos são apurados pelo participante e reportados. Você não é medido pelo que acha que levou: você é medido pelo que registrou. Sem marcação de tempo confiável em cada etapa, você não tem sequer como calcular o próprio indicador — muito menos defendê-lo.
Uma apuração de fraude vai reconstituir o trajeto de um valor. A sua parte dessa reconstituição é a sua linha do tempo.
Há um detalhe técnico aqui que o próprio Manual de Tempos explicita e que vale internalizar: a diferença entre um instante medido de um lado e o seu espelho medido do outro decorre tanto dos relógios distintos dos dois agentes quanto da solução tecnológica adotada na comunicação, que determina os pontos de aferição. Traduzindo: o relógio do arranjo e o seu relógio não são o mesmo relógio, e a divergência entre eles é esperada e mensurável.
Daí a recomendação de guardar as duas marcações de tempo em cada transição — a do arranjo e a local. Uma serve para reconstruir a verdade externa; a outra, para reconstruir a sua. Quando divergirem, e vão divergir, você quer estar com as duas na mão, e não descobrindo naquele momento que só guardou uma.
A analogia
Máquina de estados com transições registradas é o prontuário médico da transação. Ninguém apaga uma entrada do prontuário porque o diagnóstico mudou — acrescenta-se uma nova entrada, datada, assinada, e a sequência inteira permanece legível. É essa sequência que permite a outro profissional, meses depois, entender o que foi feito e por quê.
O campo status sobrescrito é o oposto disso: é um prontuário com uma linha só, sempre atualizada, sempre no presente, sem memória. Serve para saber o estado atual do paciente. Não serve para absolutamente mais nada.
O bônus que ninguém pede e todo mundo usa
E há um efeito colateral generoso: uma vez que as transições existem como fatos datados, você ganha de graça o que teria custado caro construir depois. A contagem de transações em cada estado vira funil de negócio. A idade média em cada estado vira alerta operacional. O crescimento da fila de indeterminadas vira o golden signal de que falamos anteriormente. A taxa de rejeição por código vira o termômetro da saúde da sua integração.
Nada disso precisa ser instrumentado separadamente. Tudo isso já está no log de transições, esperando alguém consultar. É a mesma estrutura respondendo, simultaneamente, ao suporte, ao plantão, ao produto e ao regulador — e essa é, no fim, a melhor defesa que existe para o custo de implementá-la direito.
O checklist
O que eu levaria para o próximo refinamento, na ordem em que eu levaria:
Log de transições de estado, não campo
statusmutável. Cada transição guarda estado anterior, estado novo, a mensagem que a causou e as duas marcações de tempo — a do arranjo e a local. Os relógios são diferentes por natureza; guardar só um deles é abrir mão da metade da prova.Identificador fim a fim gerado e persistido antes do envio, usado como chave de idempotência com o arranjo — nunca o seu ID interno.
Política de retentativa consciente da janela de 24 horas estabelecida pelo Catálogo. Dentro dela, reapresentar é seguro; fora dela, é pagamento novo.
Consumidor idempotente por padrão. Reentrega de mensagem é comportamento previsto do canal, não anomalia.
Confirmar leitura só depois de persistir o fato, nunca antes. Esse é o bug mais barato de evitar e o mais caro de descobrir.
Timeout interno estritamente menor que o do arranjo. Circuit breaker que abre depois do SPI não protege nada.
Códigos de rejeição classificados por natureza de tratamento — negócio definitivo, infraestrutura de terceiro, timeout, não terminativo — e nunca achatados em um
else.Estado indeterminado explícito no modelo, resolvido por consulta à Conta PI e jamais por suposição.
Reserva de saldo atômica com o registro da intenção, liberada apenas por rejeição explícita ou reconciliação conclusiva — nunca por timeout e nunca por job de limpeza.
Reconciliador como serviço próprio, assíncrono, com correlação entre a requisição e a transação investigada.
Fronteira do aceite documentada: tudo que pode gerar recusa é síncrono; tudo que é execução é assíncrono e garantido.
Dupla conciliação — transacional e contábil — como processo contínuo, com a divergência exposta como métrica de negócio.
Fica para a próxima
Ficaram de fora três assuntos que merecem artigo próprio, e que são a continuação natural deste: a gestão de liquidez da Conta PI — o incidente de Pix que não tem uma linha de código quebrada; os indicadores do Manual de Tempos e o que muda quando você é medido por percentil e não por média; e o MED 2.0, que acrescentou à máquina de estados um caso que quase ninguém modelou: recurso liquidado, creditado e indisponível.
Se este texto foi útil, esses três vêm na sequência. Fico por aqui neste artigo! Até o próximo!
Fontes primárias para quem quiser se aprofundar: Catálogo de Serviços do SFN — Volume VI (Pagamentos Instantâneos); Manual das Interfaces de Comunicação; Manual de Tempos do Pix; Guia de Implementação do Canal Secundário de Transmissão de Mensagens; Roteiro para Participação Direta no SPI e Abertura de Conta PI; e os Informes SPI publicados pelo Banco Central. Os valores numéricos de timeout e os indicadores de tempo são revisados periodicamente — confira sempre a versão vigente antes de implementar.


Oi Rafael, obrigado pelo artigo, é um tema que tenho grande interesse. O ponto principal que você apresenta é que o status de uma transação depende de um registro financeiro que foi criado em outro sistema.
Trabalho com desenvolvimento de soluções customizadas para o setor financeiro, para bancos, seguradoras e empresas de meios de pagamento. Gostaria de acrescentar o que acontece nos bancos que utilizam sistemas antigos, na maioria das vezes eles sistemas separados, com bases de dados separadas, um para pagamentos, outro para contabilidade, e outro para políticas de segurança e antifraude.
E cada um desses sistemas pode operar em horários diferentes, com regras diferentes, e a manutenção é feita por equiques diferentes também. Via de regra, podemos implementar uma estrutura moderna no sistema mais novo, porque é o único que pode ser construído do zero.
Traduzindo isso para prática, podem existir divergências internas antes de checar o SPI. Por exemplo, o sistema de pagamentos do banco já concluiu a transferência, mas a contabilidade ainda não registrou a saída do dinheiro porque está aguardando o horário de processamento, ou ainda o sistema de limites ainda está mantendo o valor bloqueado porque não recebeu a confirmação final.
Todos concordam com o Banco Central que o pagamento foi realizado, mas os dados internos do próprio banco não fecham entre si.
Por essa razão, o sistema de conferência (reconciliação) não pode apenas comparar dois pontos, mas sim múltiplos sistemas ao mesmo tempo. Com isso, surge uma questão fundamental: se esses sistemas internos apresentarem informações conflitantes agora, em quanto tempo a equipe consegue identificar o erro?
Por isso o sistema de reconciliação não pode comparar dois pontos apenas, e sim múltiplos sistemas simultaneamente. E aí vem a pergunta importante, se esses sistemas internos apresentarem informações conflitantes agora, quanto tempo a equipe precisa para identificar o erro?
Vou aguardar o próximo texto sobre a gestão de saldo da Conta PI.