Atestado de pessoa verificada
O módulohuman_attestation 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.
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:
check_details (o item com module igual a human_attestation), 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, peça uma emissão nova pelo replay do webhook ou pelo painel quando a emissão voltar.
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), attestation_issue_failed (a emissão falhou e a verificação seguiu sem ela) e attestation_window_closed (a decisão chegou depois de passados os 180 dias contados do dia da verificação, e não há janela para emitir). 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, assim como attestation_window_closed (passaram os 180 dias da verificação e nenhum token novo sai).
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 é 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 unique_in_service quer dizer “sem outra conta encontrada neste serviço”, e só aparece quando o módulo Detecção de múltiplas contas foi contratado no flow. A legal_age 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 legal_age. A data de nascimento em si nunca entra.
Validade. Cada atestado vale até 24 horas. Peça outro pelo replay do webhook (POST /v1/webhook-events/replay com o verification_id) ou pelo painel por até 180 dias desde o dia da verificação (em UTC), o prazo que vem em reissue_until, que nunca passa do prazo do corpo guardado do webhook. Cada token novo tem sal e assinatura novos e as mesmas informações. O iat é arredondado ao começo da hora em UTC, para o horário exato da emissão não ficar gravado no atestado, e o exp nunca passa de reissue_until. O atestado não tem vínculo com uma chave da pessoa: quem tiver o token pode apresentá-lo até ele vencer, por isso a validade é curta. Os SDKs recusam token com validade acima de 24 horas (validity_too_long).
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 (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, ["liveness"]) no TypeScript, ou present_human_attestation(sd_jwt, ["liveness"]) 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, as mesmas informações e validade de até 24 horas, então dois downloads não são o mesmo token. Passados os 180 dias, o download responde 422 attestation_window_closed, e o mesmo vale para o replay.
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.