# Atestado de pessoa verificada

<https://unifokal.com/docs/modulos/atestado-humano>

## Atestado de pessoa verificada

O módulo `atestado_humano` entrega, junto com a verificação aprovada, uma declaração assinada pela UNIFOKAL de que a pessoa passou pela prova de vida. A sua plataforma pode mostrar essa declaração a um auditor, a um regulador ou a um parceiro, e quem recebe confere a assinatura sem consultar a UNIFOKAL e sem receber nome, CPF, rosto ou data de nascimento. O formato é o SD-JWT VC (RFC 9901 com o perfil de credencial verificável da IETF), e cada informação dentro dele pode ser mostrada ou escondida separadamente.

! **Em breve.** A venda deste módulo está pausada: ele aparece na [tabela de preços](https://unifokal.com/precos) com o selo "Em breve", e o `create` de flow o recusa até a abertura. O que está descrito abaixo é o contrato que o backend já emite, para você planejar a integração.

**Quando ele é emitido.** O módulo depende de `liveness` no mesmo flow e só emite em produção, quando a verificação é aprovada pelo caminho automático. Aprovação manual na fila de revisão não emite, e o sandbox nunca emite. A cobrança acontece só quando o atestado sai: não emitido não é cobrado.

**O que chega no webhook.** O corpo do evento ganha `data.attestation`, no nível de cima de `data`, inclusive no modo de payload `minimal`. É ali que vem o token compacto, com o que você precisa para guardá-lo sem abri-lo. São três formas:

```
// data.attestation: emitido
"attestation": {
  "issued": true,
  "id": "hat_01J9ZC6Q8R3M2V7K4T5N1B0XHD",
  "format": "dc+sd-jwt",
  "sd_jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRjK3NkLWp3dCIsImtpZCI6InByZC0uLi4ifQ.eyJ...~WyJ...~WyJ...~",
  "vct": "https://unifokal.com/vct/pessoa-verificada/v1",
  "kid": "prd-0123456789ab",
  "issued_at": "2026-09-26T00:00:00Z",
  "expires_at": "2027-03-25T00:00:00Z",
  "disclosable": ["sub", "prova_de_vida", "unica_no_servico", "maioridade"] }

// data.attestation: emitido, mas o token não pôde ser montado na entrega
"attestation": {
  "issued": true,
  "id": "hat_01J9ZC6Q8R3M2V7K4T5N1B0XHD",
  "format": "dc+sd-jwt",
  "sd_jwt": null,
  "reason": "attestation_signer_not_configured",
  "vct": "https://unifokal.com/vct/pessoa-verificada/v1",
  "issued_at": "2026-09-26T00:00:00Z",
  "expires_at": "2027-03-25T00:00:00Z" }

// data.attestation: não emitido, com o motivo
"attestation": { "issued": false, "reason": "manual_decision" }
```

No bloco do módulo em `check_details` (o item com `module` igual a `atestado_humano`), o campo `data.attestation` traz os mesmos dados **sem o token**: nada de `sd_jwt` ali. Quando o atestado foi emitido mas chegou com `sd_jwt` nulo, baixe uma emissão nova pelo painel quando a emissão voltar.

```
// atestado_humano no check_details: emitido (os dados, sem o token)
{ "module": "atestado_humano", "passed": true, "outcome": "approved", "score": 100,
  "data": { "attestation": { "issued": true, "id": "hat_01J9ZC6Q8R3M2V7K4T5N1B0XHD",
            "format": "dc+sd-jwt",
            "vct": "https://unifokal.com/vct/pessoa-verificada/v1",
            "kid": "prd-0a1b2c3d4e5f",
            "issued_at": "2026-09-26T00:00:00.000Z",
            "expires_at": "2027-03-25T00:00:00.000Z",
            "disclosable": ["sub", "prova_de_vida", "unica_no_servico"] } } }

// atestado_humano no check_details: não emitido (aprovação manual)
{ "module": "atestado_humano", "passed": null, "outcome": "pending", "score": 0,
  "data": { "attestation": { "issued": false, "reason": "manual_decision" } } }
```

Os motivos de não emissão em `reason`: `verification_not_approved` (a verificação não foi aprovada automaticamente), `manual_decision` (a aprovação veio da revisão manual), `attestation_sandbox_not_issued` (sessão de sandbox), `liveness_not_approved` (a prova de vida não aprovou), `attestation_signer_not_configured` (a emissão está temporariamente indisponível do nosso lado) e `attestation_issue_failed` (a emissão falhou e a verificação seguiu sem ela). Com `issued` igual a `false` não há cobrança do módulo. O `attestation_signer_not_configured` também aparece com `issued` igual a `true` e `sd_jwt` nulo, a segunda forma acima.

**O que vai dentro.** Sempre visíveis: o emissor `iss` (`https://unifokal.com`), o tipo `vct` (o mesmo para todo cliente, sem nome de cliente, país ou produto) e a validade em `iat` e `exp`. Cada uma das informações abaixo é divulgável em separado, e a lista `disclosable` diz quais este atestado carrega:

```
"sub": "Y2Vu...43 caracteres"          // identificador aleatório da conta, só neste serviço
"prova_de_vida": { "resultado": true, "data": "2026-09-26" }
"unica_no_servico": true                  // só quando o flow tem face_unica e nenhuma outra conta foi encontrada
"maioridade": { "resultado": true, "metodo": "documento" }   // só com documento lido e rosto aprovado
```

O `sub` é um identificador aleatório, estável para a mesma conta (a sua organização, o ambiente e o `reference_id`) até o pedido de eliminação do titular, e nunca derivado de documento ou de biometria. Dois serviços diferentes recebem identificadores diferentes para a mesma pessoa. A `unica_no_servico` quer dizer "sem outra conta encontrada neste serviço", e só aparece quando o módulo [Detecção de múltiplas contas](https://unifokal.com/docs/modulos/face-unica#modulo-face-unica) foi contratado no flow. A `maioridade` sai da data de nascimento lida do documento, comparada no dia da emissão, e só aparece quando o flow leu documento com data de nascimento e o rosto bateu com ele. Idade estimada pela selfie nunca vira `maioridade`. A data de nascimento em si nunca entra.

**Validade.** 180 dias. O `iat` e o `exp` são arredondados ao começo do dia em UTC, para o horário exato da verificação não ficar gravado no atestado.

**Como conferir sem nos consultar.** O cabeçalho traz `alg` `ES256`, `typ` `dc+sd-jwt` e o `kid` da chave. As chaves públicas de produção vêm fixadas nos SDKs e também estão publicadas em [https://unifokal.com/.well-known/jwt-vc-issuer](https://unifokal.com/.well-known/jwt-vc-issuer) (o documento de metadado do emissor do SD-JWT VC). No SDK TypeScript, `verifyHumanAttestation(sdJwt)`; no SDK Python, `verify_human_attestation(sd_jwt)`. Os dois conferem a assinatura, o emissor, o tipo, a validade e cada divulgação, e recusam por padrão qualquer `kid` que não seja de produção.

**Como mostrar só uma parte.** `presentHumanAttestation(sdJwt, ["prova_de_vida"])` no TypeScript, ou `present_human_attestation(sd_jwt, ["prova_de_vida"])` no Python, devolve o mesmo token só com as divulgações escolhidas. A assinatura continua valendo, e quem recebe não vê as outras informações.

**Baixar pelo painel.** No detalhe da verificação, quem é dono ou administrador da conta baixa o atestado. Cada download é uma emissão nova, com sal e assinatura novos e as mesmas informações, então dois downloads não são o mesmo token.

**O que ele não é.** O atestado não esconde a pessoa da UNIFOKAL: como emissora, a UNIFOKAL consegue ligar um atestado à verificação que o originou. O que ele garante é que dois serviços que recebem atestados não conseguem ligar as contas entre si por meio dele. Esta versão não tem vínculo de chave: quem tem o token consegue apresentá-lo, então guarde-o como guarda uma credencial. Nada emitido em sandbox, nem os vetores de teste dos SDKs, confere contra as chaves publicadas. E o atestado diz o que a verificação encontrou no dia, sem acompanhar o que acontece com a conta depois.
