# Documento, face match e prova de vida

<https://unifokal.com/docs/modulos/identidade>

## Documento, face match e prova de vida

Os três módulos base do KYC de pessoa: **Documento (OCR)** (`cpf_ocr`), **Face Match** (`face`) e **Liveness** (`liveness`). A captura inteira acontece no widget, e nenhuma foto passa pelo seu front nem chega ao seu backend. A leitura do `cpf_ocr` é feita por fornecedor externo, com o rosto do documento tarjado antes do envio quando o detector está disponível e encontra o rosto, e com o fornecedor declarado na lista de subprocessadores. No resumo `checks` do webhook, `cpf_ocr` e `face` saem **combinados** na chave `identity` (`"pass"` ou `"fail"`) e o liveness sai como número de 0 a 1; o detalhe de cada módulo vem em `check_details`, como abaixo.

### Documento (OCR): `cpf_ocr`

Lê RG, CNH ou CIN e extrai **nome, CPF, número do documento e nascimento**. O CPF passa pelo dígito verificador ainda na leitura: CPF que não fecha vem `null`, nunca um número inválido entregue como bom. A foto do documento **não sai no webhook**: mídia fica no painel, com acesso restrito por papel.

```
// cpf_ocr no check_details: o que foi LIDO do documento
{ "module": "cpf_ocr", "passed": true, "outcome": "approved", "score": 95,
  "data": { "name": "João Silva", "cpf": "123.456.789-00",
            "birth_date": "01/03/1990", "birth_date_iso": "1990-03-01",
            "document": { "type": "cnh", "number": "07969013668",
                          "valid_until": "15/03/2029", "expired": false } } }
// "affiliation" (filiação) entra quando o documento traz o campo.
// "expired": true (vencido) | false (vigente) | null (não deu para afirmar).
// Com um módulo de Validação CPF no mesmo flow, name/birth_date preferem a fonte OFICIAL
// (o OCR vira fallback): o dado que chega é o mais confiável disponível.
```

**Documento vencido não reprova.** A validade volta em `document.valid_until` e o vencimento em `document.expired`, com **três** estados: `true` (vencido), `false` (vigente) e `null` (não deu para afirmar). O documento vencido é aceito e fica guardado como evidência; o que fazer com ele é decisão da sua política. `null` nunca é `false`.

**Na CIN e no passaporte, o impresso é conferido com a zona de leitura mecânica (MRZ).** Quando o nome, o nascimento ou o número impressos não batem com os da MRZ, a verificação vai para `review`, nunca para recusa automática, com `decision_reason: "identity_document_mrz_review"`. O motivo do módulo é `mrz_visual_mismatch`, e o painel mostra qual dos campos divergiu. Nesse caso os campos lidos do documento não vêm no `data`: não há como saber qual das duas identidades é a verdadeira, e quem decide é a pessoa que revisa.

No sandbox, com o sufixo no campo `document` do submit: `41` leva a verificação para `review` com `mrz_visual_mismatch`, e `45` aprova com o documento vencido (`document.expired: true`). A lista completa está em Sandbox.

### Face Match 1:1: `face`

Compara a selfie com a foto do documento apresentado e responde **se é a mesma pessoa**. O payload separa a **verdade biométrica** (`match` e `similarity`, contra um limiar calibrado) do score de negócio, e entrega a **qualidade de imagem** dos dois lados: com `quality` baixo você sabe que uma recusa pode ser foto ruim, não fraude.

```
// face no check_details: verdade biométrica + qualidade das imagens
{ "module": "face", "passed": true, "outcome": "approved", "score": 93,
  "data": { "match": true,          // é a mesma pessoa? (limiar biométrico, não o score de negócio)
            "similarity": 0.82,     // similaridade 0..1 entre selfie e foto do documento
            "confidence": 0.97,
            "threshold": 0.36,      // o limiar que valeu NESTA decisão (carimbado com a política)
            "quality": { "selfie": 84, "document": 71 } } }   // qualidade de imagem (FIQA) 0..100
```

### Prova de vida: `liveness`

Confirma que há **uma pessoa viva na frente da câmera**: barra foto impressa, tela e vídeo gravado. Num módulo só saem a prova **passiva** (a análise da selfie) e a **ativa** (o desafio de gestos, quando o flow pediu), mais dois vereditos de captura: `capture` (a origem dos bytes é coerente com uma câmera real?) e `active.volume` (o rosto tem volume 3D ou é um plano?). Detecção de mídia sintética **não faz parte do payload**: não há modelo de deepfake com licença que permita uso comercial de ponta a ponta, e preferimos não publicar um campo a publicar um campo que nunca tem valor. Se um dia a medição existir, o campo entra documentado aqui na mesma entrega. Repare na **unidade do `threshold`**: ele é o corte do **score do módulo**, de 0 a 100, e não um corte de `live_probability` (que é de 0 a 1). Comparar os dois inverte o sinal, e é o erro de integração mais fácil de cometer aqui. O bloco `friction` diz qual prova aquele titular fez, como explicado em [Webhooks](https://unifokal.com/docs/webhooks#webhooks).

```
// liveness no check_details: prova passiva + desafio ativo num módulo só
{ "module": "liveness", "passed": true, "outcome": "approved", "score": 96,
  "data": { "live_probability": 0.97,   // 0..1, alto = pessoa viva
            "spoof_probability": 0.03,  // 0..1, alto = artefato apresentado no lugar da pessoa
            "threshold": 80,            // ATENÇÃO à unidade: é o corte do SCORE do módulo (0..100),
                                        // NÃO um corte de live_probability. Nunca compare os dois.
            "frames_used": 3, "face_quality": 0.88,
            "capture": "coerente",      // origem dos bytes: coerente | suspeita | indeterminado
            "friction": { "mode": "adaptive", "level": 3, "actions_required": 2 },
            "active": { "challenge": ["turn_left", "look_up"], "challenge_passed": true,
                        "steps_passed": 2, "steps_total": 2,
                        "volume": "confirmado" } } }
// active = null quando o flow não pediu desafio (ou o nível adaptativo dispensou);
// volume: confirmado | plano | indeterminado (a prova de volume 3D por paralaxe)
```

#### Detecção de injeção de câmera

O veredito `capture` acima é uma capacidade com nome: **detecção de injeção de câmera**, dentro do `liveness`, sem módulo nem preço à parte. Dois caminhos independentes vigiam a origem do stream: o que o **navegador** declara na captura (automação declarada, câmera virtual, cadência implausível de frames) e o que o **pixel** denuncia (ausência de ruído de sensor, congelamento alinhado a bloco de codec, típico de vídeo injetado). A régua é deliberada: nenhum indício reprova sozinho. A recusa automática exige o único sinal forte, a automação declarada pelo próprio navegador, somado a ao menos mais um indício; os demais são sinais fracos, somados com teto, que seguram a verificação em **revisão**, nunca em recusa, porque câmera virtual e codec têm causas inocentes conhecidas. Não vendemos selo de laboratório sobre isso: a promessa é o veredito nomeado em cada verificação com prova de vida, que você audita no próprio payload.
