Gate transacional
O módulotransaction_gate avalia a transação que você nos envia e devolve allow, step_up ou deny, com o risco e as razões que dispararam. Ele olha valor fora do padrão do próprio titular, cadência, contraparte nova ou concentradora, troca de aparelho, horário, e se aquele titular já foi verificado por você aqui. Roda sobre o seu histórico, sem consórcio com outros clientes.
Disponível desde 11 de setembro de 2026. Ele aparece na tabela de preços e no GET /v1/capabilities com preço e status available, pode ser ligado num flow, e a ingestão de transações deixou de responder 422 transaction_not_supported em flow que o contenha. Até essa data as duas travas eram o preço e o registro do módulo como consumidor de transação, e elas caíram juntas: abrir só uma teria posto o módulo à venda com a porta de entrada ainda fechada.
Num flow que contenha o gate, o lote não é aceito: transactions[] responde 422 batch_not_supported_for_gate, e só a forma unitária transaction é avaliada. A razão é de produto: um gate síncrono decide um pagamento, e um lote não teria veredito. Isso não quebra integração nenhuma, e o argumento é verificável: até esta data nenhum flow podia conter o módulo, porque a criação de flow o recusava, então a regra nasce junto com a possibilidade.
Só o movimento que o próprio titular iniciou, e que não falhou, recebe veredito. São deposit, withdraw, transfer, payment e bet, com status confirmed ou pending. settlement e reversal são o ciclo de vida de um pagamento que já foi julgado, bet_profit e bet_loss são o resultado que a casa apurou, status: failed é dinheiro que não se moveu, e o evento de backfill que chega fora da janela é um pagamento que você já liquidou. Nenhum deles tem uma pergunta em aberto: todos continuam sendo ingeridos normalmente, entram na trilha, na retenção e no monitoramento, e simplesmente não geram verificação nem cobrança.
Duas escalas convivem aqui, e elas apontam para lados opostos. risk_score vai de 0 a 100 com 100 sendo o pior, e é o eixo do gate. O score do check ao lado é o de negócio, e é ternário e fixo: allow vale 100, step_up vale 40 e deny vale 0. Um não é o complemento do outro, e a conta 100 menos risk_score não reproduz o segundo.
verdict na própria chamada. Isso inclui o seu retry: repetir o mesmo external_id devolve o veredito que já foi dado na primeira vez, sem reavaliar e sem uma segunda cobrança. É o comportamento que um caminho de pagamento precisa, porque o retry de rede ali é rotina e não exceção. O external_id é único por organização e ambiente, e o veredito dele também: se o mesmo external_id for enviado por outro flow seu, a resposta é o veredito que já foi dado, sem nova avaliação e sem nova cobrança.
No sandbox o gate também decide, com a mesma forma de resposta da produção e sem gravar nada. O veredito de sandbox é determinístico e você escolhe o ramo pelos dois últimos dígitos de amount_cents. Ele sai só do valor: no sandbox o gate não consulta perfil nem lista de bloqueio, porque ali ele existe para você exercitar os três ramos do seu código, e não para julgar titular.
step_up como o caminho normal, não como o raro. Ele é a categoria que pede uma prova a mais antes de liberar o dinheiro, e é sempre a resposta quando o gate prefere não afirmar. Uma integração que só trata allow e deny está com um ramo em aberto.
O nível do gate. Na tela Motor do painel, você escolhe o nível deste gate: Conservador, Equilibrado ou Conversão, do mais rigoroso ao que mais converte. O Conservador é o padrão. Veja o efeito no seu histórico antes de ligar, e observe em sombra sem mudar nenhuma decisão. No sandbox o veredito continua saindo do valor, e o nível não muda nada.
Step-up com prova de humano. Quando o flow do gate tem um flow de prova configurado (step_up_flow_id, com um flow de reserva opcional em step_up_fallback_flow_id) e o veredito é step_up, o bloco transaction_gate passa a trazer step_up. Com available: true, session_id é a sessão de PROVA, válida por 600 segundos: monte o widget com ela, como na criação de sessão, e o titular aprova o pagamento com a passkey da conta dele ou com a reautenticação facial, conforme o fator que a conta tem (factor). O resultado chega no webhook da sessão de prova, com data.step_up_of: source é o gate que pediu a prova, verification_id é a verificação do pagamento e event_type é o tipo do evento. É por ele que você liga o desfecho da prova ao pagamento que estava esperando. A verificação do pagamento não muda por causa da prova. Com available: false, reason diz por quê (por exemplo no_factor_enrolled ou insufficient_credit) e você aplica o seu próprio desafio. Repetir o mesmo external_id devolve o mesmo step-up enquanto o pedido estiver aberto, e step_up_closed depois. No sandbox a ingestão não abre sessão de prova: o bloco vem com step_up_sandbox, na mesma forma da produção.
A sua lista de bloqueio vale aqui, em produção. Quando o reference_id da transação está na lista, o veredito é deny, com blocklist_hit em reasons. Envie sempre o reference_id do titular junto da transação: é por ele que a sua lista é consultada. No sandbox, como o veredito vem do valor, use o gatilho de amount_cents para exercitar o ramo de deny do seu código.
O reference_id e o counterparty_ref são o sujeito e o destino, e nos dois a maiúscula não muda quem é quem. User_42, user_42 e USER_42 são a mesma pessoa, e o mesmo vale para o destino: o histórico, a janela de análise e a sua lista de bloqueio enxergam um titular só. Isso importa porque um id que muda de grafia entre dois caminhos do seu backend (um derivado de e-mail, outro digitado num formulário) partiria o histórico em dois pedaços pequenos, e histórico curto derruba a confidence do veredito. O external_id é o oposto e de propósito: ele é o token do evento, comparado byte a byte, então TX-1 e tx-1 são duas transações diferentes.
A janela de análise é contada pelo relógio do nosso servidor. O occurred_at que você manda continua valendo, e é ele que descreve quando o pagamento aconteceu no seu sistema; quem decide em que janela o evento entra é o momento em que ele chega aqui. Consequência prática para a sua integração: se você faz carga retroativa, mande-a de uma vez e não espere que ela reescreva a análise de semanas passadas, porque a análise é do que chegou. Preencha o occurred_at com a hora que o seu servidor registrou, nunca com a hora que o aparelho do titular informa.
reasons pode conter razão que não pontua, e isso é de propósito. Uma razão pode declarar contexto sobre a avaliação sem somar risco: nada é tratado como “limpo por omissão”. Quem somar o tamanho de reasons como se fosse risco erra. O risco é o risk_score, de 0 a 100.
confidence não é probabilidade de fraude. É o quanto o veredito merece crédito, composto de maturidade do perfil daquele titular, completude do payload que você mandou e de quão recente é a última atividade dele. Perfil novo derruba a confiança mesmo com veredito allow. Use os dois juntos: step_up com confidence baixa é “desconfio, mas sei pouco”, e merece tratamento diferente de step_up com confiança alta.
reasons fica disponível por 180 dias contados da decisão. Depois disso, a consulta pelo painel, a reemissão e a exportação trazem o veredito, o risco, a confiança e as versões, com reasons vazia e reasons_expired: true (antes do prazo ele vem false, sempre presente). O corpo guardado de cada entrega sai junto, no mesmo prazo: depois dele, o replay e o reenvio pelo painel respondem payload_unavailable. Lista vazia com reasons_expired: true querem dizer que as razões saíram no prazo, nunca que nada disparou. No mesmo prazo a referência sai da verificação do evento: a consulta e a exportação trazem reference_id: null, e a busca pela referência deixa de achar essa verificação; o verification_id não muda, e o evento de transação segue com o prazo dele. Guarde no recebimento do webhook o que precisar guardar por mais tempo, inclusive para a avaliação anual de efetividade, se o seu regime a pede.
O que não sai daqui, e é deliberado: o limiar que separa as bandas, e o valor, a contraparte e o documento da transação. Publicar o limiar vigente transformaria o payload num oráculo contra o próprio gate. Pela mesma razão, a resposta síncrona da ingestão devolve o verdict e nada mais: as razões e as contribuições viajam só aqui, no webhook, que é superfície assinada e auditável. E calibration_version é o rastro dessa calibração: tx-cal_ mais doze caracteres em produção, e a string literal "sandbox" no ambiente de testes.
feature_set_version e calibration_version são strings opacas: guarde, nunca interprete. Elas servem para uma coisa só, e é uma coisa valiosa: reler um veredito de meses atrás sabendo que ele saiu sob exatamente aquela política. O prefixo é estável (tx-fs_ e tx-cal_); o que vem depois muda sempre que a política muda, e não carrega significado que você possa decodificar. Os valores que aparecem nos exemplos acima são ilustrativos: não compare o seu payload com eles, não os fixe em teste e não condicione comportamento a um valor específico. O que você deve fazer é guardá-los junto do desfecho, e comparar um veredito com outro veredito.
O contrato do evento: o ciclo de vida. Um movimento que você nos manda com status confirmed (o padrão) é um movimento que aconteceu. settlement liquida um evento seu que chegou pending, e quem decide a liquidação é o banco de dados, nunca a ordem de chegada: a segunda liquidação do mesmo alvo responde 409 already_settled, e uma liquidação cujo settles não aponta um evento seu, no mesmo ambiente, responde 422 unknown_settles_reference. Liquidação e reversão nunca chegam pendentes: status: pending em uma delas é 422 settlement_status_invalid. reversal nunca altera o evento revertido, que continua contando como o movimento que de fato aconteceu; a reversão é um evento próprio, e ela conta na próxima avaliação daquele titular. Em qualquer um dos dois, settles é o external_id do alvo. E status: pending só entra em flow cujo módulo aceite o ciclo pendente; nos demais ele responde 422 pending_lifecycle_not_enabled. Hoje nenhum dos módulos que consomem transação aceita o ciclo pendente: mande o evento já confirmado e, se ele for desfeito, mande a reversão.
reference_id é o titular, e precisa ser estável por pessoa: é por ele que o histórico, o perfil e a sua lista de bloqueio são lidos. Use o id do usuário no seu sistema, por exemplo um UUID. external_id é o token de idempotência do evento, comparado byte a byte: é ele que faz o seu retry devolver o mesmo veredito sem uma segunda cobrança, e ele é obrigatório (sem ele, 422 external_id_required). counterparty_ref é o destino. Nenhum dos três leva dado pessoal: CPF, CNPJ, telefone, e-mail ou chave Pix nunca entram, com ou sem máscara. Espaço nas pontas e caractere invisível não contam: a forma comparada e guardada é a canônica. Um caractere fora do conjunto aceito é 422 reference_id_charset (a mensagem nomeia o campo recusado). No reference_id e no counterparty_ref, que identificam pessoas, um valor com forma de dado pessoal é 422 pii_shaped_value; no counterparty_ref isso inclui a forma de chave Pix aleatória, então um UUID seu ali vai com prefixo ou como HMAC. O corpo do erro nunca devolve o valor recusado, só o campo e a posição no lote.
device é minimizado antes de ser guardado. Do IP fica só o prefixo de rede (/24 em IPv4, /48 em IPv6) e um derivado comparável; do fingerprint, só o derivado. Nem o IP nem o fingerprint ficam em claro, e a telemetria de aparelho tem prazo próprio, mais curto que o do evento. Mande os dois quando tiver: eles alimentam os sinais de aparelho da avaliação e nada além disso.
direction diz para que lado o dinheiro foi, do ponto de vista do titular. in entrou na conta dele, out saiu. O campo é opcional e, ausente, fica registrado como não informado: nós nunca deduzimos a direção do type, porque uma transfer pode ser as duas coisas. Qualquer outro valor é 400 validation_error. No monitoramento de PLD/FT, uma transfer ou um payment sem direction conta como saída: mande in quando o titular recebe.
instrument identifica o meio de pagamento do titular, e nunca envie o número do cartão. O bloco tem kind (card, account, wallet ou pix_key) e ref, um valor opaco seu: o token que o seu PSP já devolve, ou um HMAC que você calcula. Guardamos só o tipo e um derivado comparável do ref, nunca o valor que chegou. Um ref com forma de número de cartão (13 a 19 dígitos que fecham o dígito verificador, com ou sem separador), de CPF, CNPJ, e-mail, telefone ou chave Pix é 422 pii_shaped_value, sem o valor no corpo do erro. Não existe campo de código de segurança nem de validade, e mandar um é 400 validation_error. Se o seu token do PSP for só de dígitos, prefira mandar um HMAC dele: uma sequência numérica pode fechar o dígito verificador por acaso e ser recusada.
cash diz que a operação foi em espécie, dinheiro vivo. É booleano, o padrão é false, e não existe outro jeito de marcar espécie: o method continua sendo o meio eletrônico. Mande true só quando a operação foi mesmo em espécie, porque é ele que as regras de espécie da PLD/FT pela regra da norma leem.
amount_cents acima do teto de fábrica é 422 amount_too_large; moeda diferente de BRL é 422 currency_not_supported; occurred_at além da janela de importação é 422 event_too_old (dentro dela, o evento antigo entra normalmente e não recebe veredito, como dito acima); e o teto diário de eventos por organização é 429 daily_ingest_cap_reached, sem Retry-After porque o balde é o dia: pause, retome no dia seguinte, e saiba que o lote recusado não gravou nada. Num flow que contenha o gate, a chamada sem o bloco transaction responde 422 transaction_required, tanto na criação de sessão quanto na emissão de link hospedado: esse flow só recebe transação, e uma sessão de widget não teria o que perguntar a ele.
O flow do gate é um flow só dele. POST /v1/flows e PATCH /v1/flows/{id} recusam com 422 sync_gate_module_exclusive uma composição que junte o gate a qualquer outro módulo. A razão é a mesma que faz o gate decidir na própria chamada: a requisição que carrega o bloco transaction responde na hora e não abre jornada de captura, então um módulo de documento ou de biometria nesse flow nunca teria por onde rodar. Mantenha o seu KYC no flow que você já tem e crie um flow separado só com o gate. Os dois flows convivem no mesmo reference_id: é por ele que o gate encontra a identidade aprovada do titular. Guardamos que o titular foi aprovado no onboarding por 180 dias contados da última aprovação e, depois disso, enquanto ele tiver histórico de conta ou de transação com você. Quem fez onboarding há mais de 180 dias e ficou sem atividade desde então volta a receber no_verified_identity, e um novo onboarding reabre o registro.