Pular para o conteúdo principal

Motor Antifraude

O módulo fraud_ai não olha o documento nem o rosto: ele olha a sessão. Rede e IP, aparelho, velocidade de tentativas, o e-mail informado, a sua lista de bloqueio e o reuso na sua própria base entram num motor de regras ponderadas que devolve um risco de 0 a 100 e o motivo de cada ponto. Não há modelo treinado no caminho: são regras determinísticas de peso fixo, e o model_version que chega no seu webhook diz isso na cara (fraud-rules-1, fraud-rules-2). Ele não pede passo novo ao titular: roda sobre a sessão, o seu histórico e bases que nós hospedamos e atualizamos. Quando uma parte da análise não pode ser feita, as categorias afetadas saem como não medidas, nunca como limpas, e o campo coverage diz isso.
Este é o único módulo cujo bloco não se chama data. No check_details ele vem em fraud_assessment. Um parser que procura data em todo item lê undefined exatamente neste, e é o erro de integração mais caro da página, porque ele passa despercebido até o dia em que você for auditar por que o score não bate.
Atenção à direção da escala, que é invertida em relação ao resto do payload. fraud_score é risco: 0 é ótimo e 100 é péssimo. O score do próprio check, ao lado dele, segue a convenção do resto da API (alto é bom). Em produção, quando o módulo mediu, os dois são exatamente complementares, score = 100 menos fraud_score (quando a análise não pôde ser coletada, o fraud_score sai null e o score sai 0), então ver "score": 92 junto de "fraud_score": 8 não é contradição, é a mesma medida em dois sentidos. Ramifique por level ou por decision, que não têm essa ambiguidade. level tem três valores, low, medium e high, e é ele que determina o desfecho do check: low aprova (passed: true), medium fica indeterminado (passed: null, outcome: "pending") e high reprova o módulo (passed: false). O campo decision é a mesma coisa na língua da decisão: approve, review e decline. Reprovar o módulo não é reprovar a verificação: o fraud_ai é um portão suave, então decision: "decline" aqui dentro não recusa nada sozinho, entra ponderado com os outros módulos. Quem decide a verificação é o status no topo do webhook, nunca este campo.
Há uma diferença entre “olhamos e está limpo” e “não conseguimos olhar”, e o bloco diz qual dos dois foi. Cada categoria (device, velocity, geo, email, ip_risk, phone, company, identity_coherence, device_intel) sai null quando não foi observada nesta verificação, nunca "ok" por omissão. O campo coverage.band resume com quanto da análise esta verificação foi avaliada. complete: a análise rodou inteira para o que este fluxo coleta. partial: uma parte da análise não pôde ser feita nesta verificação; o resultado vale para o que foi analisado e segue as regras de sempre, e você decide se quer um passo a mais, como um código por e-mail ou SMS ou a revisão do seu time. insufficient: a sessão rendeu pouca informação para afirmar que o cadastro está limpo; passed sai null e decision sai review, e é isso que o terceiro exemplo acima mostra. Se nada acendeu, o módulo não conta a favor da aprovação: ele fica fora da decisão da verificação, nem para aprovar nem para reprovar, e em alguns casos a verificação segue para revisão sem cobrança. coverage.sufficient continua valendo e é false só em insufficient. Ramifique por band. reasons_text traz as mesmas razões com texto pronto para tela. Ramifique sempre por reasons, nunca pelo texto: o código é o contrato e é estável, o texto pode mudar de redação. applied_rules lista as regras que contribuíram para o fraud_score, as mesmas de reasons: cada uma com o código, a família a que ela pertence e o texto. O campo weight sai sempre null, e families também sai sempre null: a explicação é qual regra pesou, não quanto. engine_version diz qual motor produziu aquelas razões, para você conseguir comparar duas verificações separadas no tempo. applied_rules sai null enquanto o motor de soma plana estiver decidindo, porque ele não conhece família por regra. As chaves já saem hoje para que a troca de motor não mude a forma da resposta. O bloco signals traz duas coisas de propósito: o estado por categoria, que é legível e curto, e a contagem crua que o motor usou (device_reuse_count, ip_reuse_count). Sem a contagem você leria "device": "flagged" sem saber se foram dois ou quarenta cadastros no mesmo aparelho, que é a diferença entre um celular de família e uma fazenda de contas. null na contagem significa não medido naquela verificação, nunca zero. Já reasons é a lista de códigos de regra que dispararam, um vocabulário estável (device_reuse, velocity, impossible_travel, blocklist_hit, disposable_email e outros) do qual as categorias acima são derivadas. Ele pode ganhar códigos novos: trate como lista aberta. O que não está aqui, e é deliberado. Detecção de mídia sintética não vive neste módulo nem em nenhum outro hoje: não existe modelo de deepfake com licença comercial de ponta a ponta, e não publicamos campo sem medição por trás. E nenhum valor de identificador viaja no bloco: nem IP, nem e-mail, nem o fingerprint do aparelho, nem o hash de nenhum deles. Sai a categoria e sai a contagem, que é o que sustenta a sua decisão sem transformar o webhook num oráculo sobre terceiros. Em sandbox este módulo é o menos realista da API, e é melhor você saber por quê. Os sinais de risco vêm do seu histórico real (aparelhos, IPs e tentativas da sua organização), e no ambiente de testes esse histórico não existe. Então o bloco fraud_assessment sai sempre no caminho limpo, com level: "low", fraud_score baixo e reasons: [], enquanto passed, outcome e score continuam seguindo o sufixo do documento, como no resto do sandbox. Ou seja: lá, e só lá, dá para ver decision: "decline" ao lado de level: "low", e a relação score = 100 menos fraud_score não vale. Não programe a sua conciliação contra o par que o sandbox mostra; programe contra os campos, que são os mesmos nos dois ambientes.