Biometrics
AlphaProva de vida e match facial com motor próprio, no contrato dos provedores certificados
A pessoa diante da câmera está viva e é a mesma do documento? Sessão, página de captura hospedada, decisão explicável com score, limiar e versão de modelo, e evidência assinada — com um nível de garantia declarado, para que a aprovação do motor aberto nunca pareça a de um provedor certificado.
- Fintechs que abrem conta ou concedem crédito e precisam de KYC com prova de vida antes de fechar contrato com FaceTec ou Unico
- Produtos que confirmam operação de alto valor pela face de um cliente já cadastrado, como segundo fator
- Times que precisam provar à auditoria como cada verificação foi decidida, com qual modelo, qual limiar e qual evidência
- O SDK do provedor colado direto no app, com a decisão tomada no front
- A selfie comparada com o documento sem prova de vida, que uma foto impressa engana
- O contrato com provedor fechado antes de saber quanto a própria base vai exigir
- Documentoscopia — lê MRZ e QR, mas não diz se o RG é verdadeiro
- Consulta a bureau, score ou listas restritivas
- Reconhecimento 1:N em multidão ou vigilância
- Um cadastro de clientes — guarda o rosto e a decisão, e se liga ao Customers pelo customerId
33 endpoints em 5 recursos.
/biometrics/api/v1/sessions/biometrics/biometrics/api/v1/enrollments/biometrics/api/v1/biometrics/api/v1Telas
o que o usuário vê











Resumo executivo
O Biometrics responde a duas perguntas sobre quem está diante da câmera: está vivo? e é a mesma pessoa? — do documento, na abertura de conta, ou do cadastro facial, na autenticação.
O sistema da empresa abre uma sessão, manda a pessoa para a página de captura hospedada pelo bloco e recebe de volta uma decisão explicada: cada verificação com o score, o limiar usado e a versão do modelo, mais uma evidência assinada que qualquer um confere sem depender da Catalisa.
Por trás, um motor próprio, o face-engine, montado sobre modelos de pesos
abertos e rodando na nossa infraestrutura: nenhuma imagem de rosto sai para
terceiro. FaceTec, Unico e Serpro Datavalid estão declarados no mesmo contrato
e entram como drivers quando houver credencial.
| Sessões genuínas medidas | 59, nenhuma reprovada pelo liveness passivo |
| Match contra o cadastro facial feito pelo bloco | 0,95 |
| Avaliação de uma sessão de celular | ~8 s |
| Imagens de rosto enviadas a terceiros | nenhuma |
Segurança em seis camadas, nível de garantia ENHANCED, em conformidade com os requisitos da ISO/IEC 30107-3
| Camada | Contra o quê |
|---|---|
| Liveness passivo | Foto impressa, foto ou vídeo exibidos numa tela |
| Desafio ativo sorteado | Vídeo gravado e reinjetado — gestos, ordem e duração mudam a cada sessão |
| Continuidade de identidade | Troca de pessoa durante a captura |
| Integridade da captura | Câmera virtual e troca de dispositivo entre tentativas, que vão para revisão |
| Bloqueio por titular | Tentativas em série contra o mesmo CPF |
| Evidência assinada em Ed25519 | Alteração do resultado depois da decisão |
O Biometrics segue os requisitos e as recomendações das normas de mercado para biometria facial — ISO/IEC 30107-1 e 30107-3 na detecção de ataque, ISO/IEC 19795-1 nas métricas de desempenho, ICAO 9303 na leitura de documento e LGPD no tratamento do dado. Não é homologado nem certificado por laboratório: a certificação iBeta está no roadmap, e até lá a conformidade é declarada pela Catalisa e demonstrada pelo relatório de avaliação no formato da ISO/IEC 30107-3.
Em teste de injeção por câmera virtual, um vídeo genuíno de uma sessão aprovada,
reinjetado numa sessão nova, foi reprovado pelo desafio sorteado. Onde a operação
exigir certificação de laboratório, os drivers CERTIFIED entram no mesmo
contrato, sem mudar a integração.
O problema
negócioAbrir conta pelo celular exige saber que a pessoa é quem diz ser. O jeito comum de resolver é comparar uma selfie com a foto do documento — e uma selfie é só uma foto. Uma foto impressa do rosto de outra pessoa passa, e uma foto na tela do celular também.
Os provedores que resolvem isso bem, como FaceTec, iProov e Unico, cobram por verificação em contrato anual, e o SDK deles decide dentro do app. Três consequências aparecem depois:
- A decisão é caixa-preta. Quando a verificação reprova um cliente bom, o time não sabe se foi luz, câmera, limiar ou modelo. Quando aprova um fraudador, também não.
- A auditoria não tem o que ver. A LGPD trata biometria como dado sensível, e "o fornecedor aprovou" não é evidência de como a decisão foi tomada.
- Trocar de fornecedor é reescrever. Cada SDK tem fluxo, formato de resposta e vocabulário de erro próprios, e a integração vira dívida no dia em que o contrato muda.
Proposta de valor
negócio- Prova de vida em camadas. Passiva, por modelo que distingue pele de papel e de tela, e ativa, com gestos sorteados em quantidade, ordem e duração a cada sessão.
- Continuidade de identidade. O rosto do começo do vídeo tem que ser o do fim: trocar de pessoa no meio reprova.
- Decisão explicável. Nove status em vez de dois.
INCONCLUSIVEvai para revisão humana (LGPD art. 20);RETRY_ALLOWEDé experiência de uso, porque foto escura não é fraude. - Evidência assinada. Hash do vídeo, do melhor quadro, da referência, dos checks e da política, assinado com chave Ed25519 da organização.
- Garantia declarada. O driver diz o que entrega —
BASIC,ENHANCEDouCERTIFIED— e a política exige um mínimo. O motor aberto éENHANCED. - Página de captura pronta, com a cor, o logo e os textos da empresa, contagem de preparo antes de gravar e guia ao vivo que diz o que falta no gesto.
- Tudo parametrizável pela empresa: quantos gestos, quais, quanto tempo cada um, limiares, bloqueio por titular, retenção. Sem deploy.
Casos de uso reais
negócioAbertura de conta com documento
[Cenário ilustrativo] Fintech de crédito pessoal abre conta pelo celular e precisa de KYC com prova de vida.
A dor. A comparação da selfie com a foto do documento, sozinha, aprova uma foto impressa, e o fornecedor certificado custa mais por verificação do que a margem da primeira operação.
A solução com o bloco. Sessão ONBOARDING com a foto do documento como
referência e enrollOnApprove: true. Na criação, o bloco lê a MRZ do passaporte
ou o QR da CNH digital e recusa documento vencido se a política pedir. Depois
avalia liveness e match e, ao aprovar, já grava o cadastro facial da pessoa.
O resultado. Validado em staging com passaporte e rosto reais: aprovado na primeira tentativa, match 0,702 contra o documento.
Confirmação de operação de alto valor
[Cenário ilustrativo] O mesmo cliente, meses depois, pede um saque acima do limite habitual.
A dor. SMS e senha provam posse de aparelho e conhecimento, não presença.
A solução com o bloco. Sessão AUTHENTICATION com referência
ENROLLED_TEMPLATE. A comparação é contra o rosto que o próprio bloco capturou,
não contra a foto do documento.
O resultado. Match 0,951 contra o cadastro feito pelo próprio bloco — o desenho é "documento uma vez, cadastro facial depois". E cinco falhas seguidas do mesmo CPF em uma hora bloqueiam o titular por 30 minutos.

Calibrar antes de contratar
[Cenário ilustrativo] Antes de decidir qual provedor contratar, é preciso saber como a própria base se comporta: quantos genuínos o sistema reprova, e por quê.
A solução com o bloco. Link de convite (POST /invites) compartilhado com
voluntários. Cada pessoa rotula o que vai mostrar à câmera — ela mesma, uma foto
impressa, uma tela, uma máscara — e o relatório cruza o rótulo com a decisão.
O resultado. Na medição em staging, nenhuma das 59 sessões genuínas foi reprovada pelo liveness; os ajustes ficaram no tempo e na leitura dos gestos, cada um com teste de regressão sobre as sessões reais gravadas.

Mercado e diferenciais
negócioO mercado de biometria facial tem três faixas. Os provedores certificados — FaceTec, iProov — vendem prova de vida auditada por laboratório. As redes de identidade — Unico — vendem a base compartilhada de rostos e fraudes. E a fonte primária estatal — Serpro Datavalid — compara com a foto da base oficial, sem prova de vida.
O bloco é a camada que fala com todos, com um motor próprio que atende sem contrato de terceiro.
| Integração direta com o SDK | Com o Biometrics | |
|---|---|---|
| Onde a decisão acontece | No app, dentro do SDK | No servidor, com a política da empresa |
| Por que reprovou | Código do fornecedor | Motivo, score, limiar e modelo por verificação |
| Evidência para auditoria | Log da aplicação | Hashes assinados em Ed25519, verificáveis fora da Catalisa |
| Revisão humana | A empresa monta | Status INCONCLUSIVE e evento próprio |
| Trocar de fornecedor | Reescrever a captura e o parser | Trocar a configuração |
| Custo em volume baixo | Mínimo contratual | Motor próprio, sem custo por consulta a terceiro |
Quando escolher o concorrente. Quando a regulação ou um parceiro exigir
certificação de laboratório, a FaceTec ou a iProov entram como driver CERTIFIED
no próprio bloco, mantendo política, evidência e trilha. Para somar o sinal de
fraude compartilhado entre instituições, a Unico entra da mesma forma.
Modelo de cobrança e ROI
negócioUnidade: sessão de verificação avaliada. Precificação em definição.
Drivers de custo: sessões avaliadas, tentativas por sessão (o limite padrão é 3), o fornecedor quando não for o motor próprio, e a retenção de mídia, que por padrão é de 30 dias.
Com o motor próprio não há custo por consulta a terceiro: o custo é CPU. Uma sessão de celular leva cerca de 8 segundos de avaliação num nó de 4 vCPUs, e o motor escala horizontalmente por réplica.
Com um driver externo, o custo do fornecedor fica registrado por sessão. Em
ambos os casos o consumo vai ao Billing no medidor biometrics.verification, com
o id da sessão como chave de idempotência.
Arquitetura
flowchart TB
TENANT["Sistema da empresa<br/>backend"]
PESSOA["Pessoa<br/>navegador ou app"]
subgraph BIO["Building Block Biometrics"]
SESSAO["POST /sessions<br/>política e sorteio dos gestos"]
PAGINA["Página de captura<br/>token de uso único"]
SUBMIT["Submissão<br/>vídeo e telemetria"]
DECISAO["Decisão<br/>checks, limiares, status"]
EVID["Evidência<br/>hashes assinados Ed25519"]
end
ENGINE["face-engine<br/>motor próprio"]
EXT["FaceTec, Unico, Serpro<br/>drivers declarados"]
FS["File Storage<br/>vídeo e documento"]
EVT["Webhooks Engine<br/>biometrics.session.completed"]
TENANT --> SESSAO
SESSAO -- "captureUrl" --> TENANT
TENANT -- "link ou iframe" --> PESSOA
PESSOA --> PAGINA
PAGINA --> SUBMIT
SUBMIT --> ENGINE
SUBMIT -.-> EXT
SUBMIT --> FS
ENGINE --> DECISAO
DECISAO --> EVID
EVID --> EVT
EVT --> TENANTO motor. O face-engine é um serviço separado, em Python, que só o bloco
alcança, com bearer próprio. Ele encadeia modelos de pesos abertos, todos
publicados por terceiros com licença permissiva:
| Etapa | Modelo | Licença |
|---|---|---|
| Detecção de rosto | YuNet | MIT |
| Liveness passivo | MiniFASNet | Apache-2.0 |
| Pontos do rosto e gestos | MediaPipe Face Landmarker | Apache-2.0 |
| Comparação de rostos | Facenet512 | MIT |
| MRZ do passaporte e QR da CNH-e | Tesseract e zxing-cpp | Apache-2.0 |
Os pesos são fixados por hash em models.lock.json: trocar um modelo muda a
versão do motor, e a versão sai em todo check.
O que o bloco não faz, e quem faz
| Não faz | Quem faz |
|---|---|
| Guardar o vídeo e o documento | File Storage |
| Entregar o resultado por HTTP ao sistema da empresa | Webhooks Engine |
| Consultar CPF em bureau | Bureaus |
| Extrair campos do documento para o cadastro | Data Extraction |
| Dizer se o documento é autêntico | Documentoscopia — fora do escopo |
Conceitos e modelo de dados
Glossário
| Termo | Significa |
|---|---|
| Sessão | Uma verificação, do pedido à decisão. Aceita até maxAttempts tentativas |
| Fluxo | ONBOARDING (com documento), AUTHENTICATION (com cadastro facial), ENROLLMENT (só cadastra), LIVENESS_ONLY e DEDUP (reservado) |
| Referência | Contra o que o rosto é comparado: DOCUMENT_IMAGE, FILE, ENROLLED_TEMPLATE, PROVIDER_BASE |
| Template | O cadastro facial: o vetor do rosto, cifrado com chave própria. Nunca sai do bloco |
captureToken | Token curto, de uso único por tentativa, que é tudo o que a página de captura recebe |
| Check | Uma verificação: quality.capture, liveness.passive, liveness.active, identity.continuity, match.1_1, capture.integrity |
| Garantia | BASIC, ENHANCED ou CERTIFIED. Declarada pelo driver, exigida pela política |
Modelo de dados
| Modelo Prisma | Propósito | Campos-chave |
|---|---|---|
BiometricsProviderConfig | Fornecedor por organização | providerType, credencial cifrada, appearance, allowedEmbedHosts |
BiometricsPolicy | Política por organização | limiares, challenge, lockout, retention, minAssurance |
BiometricsSession | A verificação e a decisão | flow, status, titular em HMAC, decision, evidência |
BiometricsCapture | Cada tentativa | hashes, fileId da mídia, telemetria, checks |
BiometricsTemplate | Cadastro facial | vetor cifrado, versão do modelo, titular em HMAC |
BiometricsSigningKey | Chave Ed25519 da organização | keyId, chave pública, privada cifrada |
BiometricsUsage | Consumo medido | sessão, fornecedor, custo |
Status de uma sessão
| Status | Terminal | O que fazer |
|---|---|---|
SESSION_OPEN | não | Aguardando a captura |
PROCESSING | não | Avaliando |
APPROVED | sim | Seguir |
REJECTED | sim | Recusar. Conta para o bloqueio do titular |
INCONCLUSIVE | sim | Revisão humana (ou recusa, se a política pedir onInconclusive: REJECT) |
RETRY_ALLOWED | não | A página oferece nova tentativa com o motivo |
EXPIRED | sim | Abrir nova sessão |
CANCELLED | sim | Cancelada pela empresa |
ERROR | não | Falha do motor. O único que merece nova tentativa automática |
Os motivos da decisão formam um vocabulário fechado, com namespace, que a
esteira da empresa pode automatizar: quality.too_dark,
liveness.passive.failed, liveness.active.gesture_missing,
liveness.active.timeout, identity.continuity_broken, match.below_threshold,
match.gray_zone, capture.injection_suspected, capture.device_changed,
policy.assurance_insufficient, subject.locked, entre outros — 27 no total.
Referência da API
Todas as rotas ficam sob /biometrics. As de /api/v1 exigem JWT com
organizationId; as de captura e convite aceitam só o próprio token.
Sessões
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /biometrics/api/v1/sessions | Abre a sessão e devolve o handoff | BIOMETRICS_VERIFY |
GET | /biometrics/api/v1/sessions | Lista | BIOMETRICS_READ |
GET | /biometrics/api/v1/sessions/:id | O envelope completo | BIOMETRICS_READ |
GET | /biometrics/api/v1/sessions/:id/evidence | Hashes e assinatura | BIOMETRICS_READ |
GET | /biometrics/api/v1/sessions/:id/evidence/verify | Confere a assinatura no servidor | BIOMETRICS_READ |
POST | /biometrics/api/v1/sessions/:id/cancel | Cancela | BIOMETRICS_VERIFY |
POST | /biometrics/api/v1/sessions/:id/captures | Submissão por SDK próprio, com o captureToken | token |
POST /biometrics/api/v1/sessions
Request
{
"flow": "ONBOARDING",
"purpose": "abertura de conta",
"subjectRef": { "type": "CPF", "value": "529.982.247-25" },
"reference": { "source": "DOCUMENT_IMAGE", "fileId": "7c1e…" },
"enrollOnApprove": true
}{
"flow": "ONBOARDING",
"purpose": "abertura de conta",
"subjectRef": { "type": "CPF", "value": "529.982.247-25" },
"reference": { "source": "DOCUMENT_IMAGE", "fileId": "7c1e…" },
"enrollOnApprove": true
}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
flow | enum | sim | ONBOARDING, AUTHENTICATION, ENROLLMENT, LIVENESS_ONLY |
purpose | string | sim | Finalidade, de 3 a 120 caracteres. Vai para a trilha |
legalBasis | string | não | Base legal declarada |
subjectRef | objeto | não | CPF do titular. Gravado como HMAC e máscara; ativa o bloqueio por titular |
reference | objeto | depende do fluxo | DOCUMENT_IMAGE ou FILE com fileId; ENROLLED_TEMPLATE com templateId |
customerId | uuid | não | Vínculo com o Customers |
providerConfigId | uuid | não | Força um fornecedor; senão, vale o padrão da organização |
appearance | objeto | não | Cores, logo, textos e modo da página, só para esta sessão |
metadata | objeto | não | Até 10 rótulos livres. Nunca CPF |
enrollOnApprove | booleano | não | Grava o cadastro facial ao aprovar. Só ONBOARDING e ENROLLMENT |
Resposta 201
{
"data": {
"sessionId": "5b0e…",
"flow": "ONBOARDING",
"provider": "OPENSOURCE",
"assurance": "ENHANCED",
"status": "SESSION_OPEN",
"attempt": 0,
"maxAttempts": 3,
"subjectRef": { "type": "CPF", "hmac": "…", "masked": "***.***.247-25" },
"reference": {
"source": "DOCUMENT_IMAGE",
"portraitExtracted": true,
"document": { "authenticity": "mrz-valid", "mrz": { "…": "mascarado" }, "qr": null }
},
"handoff": {
"captureUrl": "https://biometrics.bb.stg.catalisa.app/biometrics/capture/eyJ…",
"captureToken": "eyJ…",
"captureMode": "RAW_FRAMES",
"challenge": { "script": ["SMILE", "TURN_LEFT", "BLINK"], "timeoutMs": 22000, "slotsMs": [5200, 6100, 4800] },
"expiresAt": "2026-09-18T15:00:00.000Z"
},
"expiresAt": "2026-09-18T15:00:00.000Z"
}
}{
"data": {
"sessionId": "5b0e…",
"flow": "ONBOARDING",
"provider": "OPENSOURCE",
"assurance": "ENHANCED",
"status": "SESSION_OPEN",
"attempt": 0,
"maxAttempts": 3,
"subjectRef": { "type": "CPF", "hmac": "…", "masked": "***.***.247-25" },
"reference": {
"source": "DOCUMENT_IMAGE",
"portraitExtracted": true,
"document": { "authenticity": "mrz-valid", "mrz": { "…": "mascarado" }, "qr": null }
},
"handoff": {
"captureUrl": "https://biometrics.bb.stg.catalisa.app/biometrics/capture/eyJ…",
"captureToken": "eyJ…",
"captureMode": "RAW_FRAMES",
"challenge": { "script": ["SMILE", "TURN_LEFT", "BLINK"], "timeoutMs": 22000, "slotsMs": [5200, 6100, 4800] },
"expiresAt": "2026-09-18T15:00:00.000Z"
},
"expiresAt": "2026-09-18T15:00:00.000Z"
}
}Perfil avaliado. Todo envelope do motor próprio traz evaluation: o perfil de
avaliação ISO/IEC 30107-3 a que a sessão se refere, se ela rodou dentro dele
(conforms) e, se não, o que saiu (violations). A garantia CERTIFIED vale só
para sessão conforme a um perfil com laudo emitido. GET /policy e PUT /policy
devolvem a mesma conformidade em meta.evaluationProfiles, e GET /catalog lista
os perfis.
"evaluation": {
"profileId": "pad-l1-2026-09",
"label": "Candidato ISO/IEC 30107-3 Nível 1",
"standard": "ISO/IEC 30107-3",
"conforms": true,
"certified": false,
"violations": []
}"evaluation": {
"profileId": "pad-l1-2026-09",
"label": "Candidato ISO/IEC 30107-3 Nível 1",
"standard": "ISO/IEC 30107-3",
"conforms": true,
"certified": false,
"violations": []
}Depois da decisão, GET /sessions/:id traz decision com outcome, reasons
e reviewRequired; checks[] com status, score, threshold, reasons e o
engine (nome, modelo e versão) de cada verificação; evidence com os hashes e a
assinatura; e enrollment.templateId quando enrollOnApprove gravou o cadastro.
Erros
| Status | Código | Quando |
|---|---|---|
400 | VALIDATION | Fluxo sem a referência que exige, CPF inválido, documento vencido com rejectExpiredDocument, referência sem rosto utilizável |
400 | BAD_REQUEST | Garantia do driver abaixo de minAssurance, ou capacidade que o driver não tem (provider.not_supported) |
403 | FORBIDDEN | Sem permissão, ou titular bloqueado (subject.locked) |
404 | NOT_FOUND | Cadastro facial ou arquivo de referência inexistente ou de outra organização |
503 | SERVICE_UNAVAILABLE | Motor indisponível |
Captura e convite (públicas, por token)
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /biometrics/capture/:token | A página de captura | token |
GET | /biometrics/capture/:token/state | Estado da sessão, para a página | token |
GET | /biometrics/capture/:token/logo | O logo da empresa | token |
GET | /biometrics/capture/:token/guide/:name | Arquivos do guia ao vivo, com hash fixo | token |
POST | /biometrics/capture/:token/submit | O vídeo e a telemetria | token |
POST | /biometrics/api/v1/invites | Cria um link de convite | BIOMETRICS_ADMIN |
GET | /biometrics/invite/:token | A página do convite | token |
POST | /biometrics/invite/:token/start | Abre uma sessão a partir do convite | token |

Cadastro facial
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /biometrics/api/v1/enrollments | Cria o cadastro a partir de uma sessão aprovada | BIOMETRICS_ENROLL |
GET | /biometrics/api/v1/enrollments/:id | Metadados do cadastro, nunca o vetor | BIOMETRICS_READ |
DELETE | /biometrics/api/v1/enrollments/:id | Revoga | BIOMETRICS_ENROLL |
POST | /biometrics/api/v1/enrollments/rekey | Recifra os cadastros com a chave nova | BIOMETRICS_ADMIN |
Evidência e direitos do titular
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /biometrics/api/v1/evidence-keys | Chaves públicas Ed25519 da organização | BIOMETRICS_READ |
POST | /biometrics/api/v1/evidence-keys/rotate | Gira a chave de assinatura | BIOMETRICS_ADMIN |
POST | /biometrics/api/v1/subjects/report | O que existe sobre um CPF (LGPD art. 18) | BIOMETRICS_ADMIN |
POST | /biometrics/api/v1/subjects/erase | Apaga os dados biométricos de um CPF | BIOMETRICS_ADMIN |
O CPF vai no corpo, nunca na URL, para não aparecer em log de acesso.
Fornecedores, política e consumo
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /biometrics/api/v1/providers | Cadastra o fornecedor | BIOMETRICS_ADMIN |
GET | /biometrics/api/v1/providers | Lista | BIOMETRICS_READ |
GET | /biometrics/api/v1/providers/:id | Detalhe | BIOMETRICS_READ |
PATCH | /biometrics/api/v1/providers/:id | Atualiza, inclusive a aparência | BIOMETRICS_ADMIN |
DELETE | /biometrics/api/v1/providers/:id | Remove | BIOMETRICS_ADMIN |
POST | /biometrics/api/v1/providers/:id/test-connection | Testa o motor ou a credencial | BIOMETRICS_ADMIN |
GET | /biometrics/api/v1/policy | A política da organização | BIOMETRICS_READ |
PUT | /biometrics/api/v1/policy | Troca a política | BIOMETRICS_ADMIN |
GET | /biometrics/api/v1/catalog | Drivers, capacidades e garantia de cada um | BIOMETRICS_READ |
GET | /biometrics/api/v1/usage | Consumo por período | BIOMETRICS_READ |
Início rápido
1. Autenticar
TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"email":"SEU-EMAIL","password":"SUA-SENHA","organizationId":"SUA-ORG"}' \
| jq -r .accessToken)
BIO=https://biometrics.bb.stg.catalisa.app/biometrics/api/v1TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"email":"SEU-EMAIL","password":"SUA-SENHA","organizationId":"SUA-ORG"}' \
| jq -r .accessToken)
BIO=https://biometrics.bb.stg.catalisa.app/biometrics/api/v12. Cadastrar o motor próprio como padrão
curl -X POST $BIO/providers \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "motor-proprio", "providerType": "OPENSOURCE", "isDefault": true }'curl -X POST $BIO/providers \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "motor-proprio", "providerType": "OPENSOURCE", "isDefault": true }'3. Abrir uma sessão de prova de vida
curl -s -X POST $BIO/sessions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "flow": "LIVENESS_ONLY", "purpose": "teste do inicio rapido" }' \
| jq '.data | {sessionId, captureUrl: .handoff.captureUrl}'curl -s -X POST $BIO/sessions \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "flow": "LIVENESS_ONLY", "purpose": "teste do inicio rapido" }' \
| jq '.data | {sessionId, captureUrl: .handoff.captureUrl}'4. Abrir o captureUrl no celular e seguir a página. Ela pede a câmera, mostra
o primeiro gesto com uma contagem de 2,5 s antes de gravar, guia os gestos
sorteados e envia sozinha.

5. Ler a decisão
curl -s $BIO/sessions/$SESSION_ID -H "Authorization: Bearer $TOKEN" \
| jq '.data | {status, decision, checks: [.checks[] | {kind, status, score, threshold}]}'curl -s $BIO/sessions/$SESSION_ID -H "Authorization: Bearer $TOKEN" \
| jq '.data | {status, decision, checks: [.checks[] | {kind, status, score, threshold}]}'{
"status": "APPROVED",
"decision": { "outcome": "APPROVED", "reasons": [], "reviewRequired": false },
"checks": [
{ "kind": "quality.capture", "status": "PASSED", "score": 0.91, "threshold": 0.6 },
{ "kind": "liveness.passive", "status": "PASSED", "score": 0.97, "threshold": 0.8 },
{ "kind": "liveness.active", "status": "PASSED", "score": 1, "threshold": 1 },
{ "kind": "identity.continuity", "status": "PASSED", "score": 0.95, "threshold": 0.7 }
]
}{
"status": "APPROVED",
"decision": { "outcome": "APPROVED", "reasons": [], "reviewRequired": false },
"checks": [
{ "kind": "quality.capture", "status": "PASSED", "score": 0.91, "threshold": 0.6 },
{ "kind": "liveness.passive", "status": "PASSED", "score": 0.97, "threshold": 0.8 },
{ "kind": "liveness.active", "status": "PASSED", "score": 1, "threshold": 1 },
{ "kind": "identity.continuity", "status": "PASSED", "score": 0.95, "threshold": 0.7 }
]
}
Receitas
Mais tempo para cada gesto. Público mais velho ou celular mais lento pedem janela maior. A política aceita o intervalo sorteado por gesto e o teto do vídeo:
curl -X PUT $BIO/policy -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "challenge": { "gestures": 2, "gesturesMax": 3, "slotMinMs": 5500, "slotMaxMs": 7500, "timeoutMs": 24000 } }'curl -X PUT $BIO/policy -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "challenge": { "gestures": 2, "gesturesMax": 3, "slotMinMs": 5500, "slotMaxMs": 7500, "timeoutMs": 24000 } }'O vídeo inteiro não passa de 24 segundos — é o limite do motor. Menos gestos com mais tempo costumam reprovar menos gente do que mais gestos com pouco tempo.

Escolher os gestos. challenge.pool restringe o catálogo; o padrão tem sete
(virar à esquerda e à direita, piscar, sorrir, abrir a boca, acenar com a cabeça,
erguer as sobrancelhas). approachEnabled: true inclui aproximar o rosto da
câmera, que funciona bem no celular e mal no notebook.
Página com a cara da empresa. appearance no fornecedor vale para todas as
sessões; na sessão, só para ela. Cores, raio dos cantos, fonte de uma lista
fechada, logo por fileId, modo claro, escuro ou automático, textos por idioma e
redirectUrl. Nada vira CSS ou JavaScript livre.

Embutir em iframe. Liste a origem em allowedEmbedHosts do fornecedor e use
allow="camera". A página avisa o andamento por postMessage — nunca o
resultado:
event | Quando |
|---|---|
ready | Página carregada, sessão capturável |
capturing | Câmera liberada, gravação começou |
step:<GESTO> | Cada passo do roteiro, com index, total e durationMs |
submitted | Vídeo enviado |
retry | Nova tentativa; a página recarrega sozinha |
done | Sessão encerrada — leia o envelope pelo servidor |
expired | Sessão expirada |
error | Câmera negada ou motor instável, com reason |
<iframe id="bio" src="CAPTURE_URL" allow="camera"></iframe>
<script>
window.addEventListener('message', (e) => {
if (e.origin !== 'https://biometrics.bb.stg.catalisa.app' || e.data?.source !== 'catalisa-biometrics') return
if (e.data.event === 'done') consultarSessaoNoBackend()
})
</script><iframe id="bio" src="CAPTURE_URL" allow="camera"></iframe>
<script>
window.addEventListener('message', (e) => {
if (e.origin !== 'https://biometrics.bb.stg.catalisa.app' || e.data?.source !== 'catalisa-biometrics') return
if (e.data.event === 'done') consultarSessaoNoBackend()
})
</script>Verificar a evidência sem a Catalisa. Baixe a chave pública em
GET /evidence-keys e confira a assinatura de GET /sessions/:id/evidence. A
mensagem assinada é sessionId|attempt|bundleHash|signedAt, e o bundleHash já
resume checks, artefatos, política e roteiro — basta openssl pkeyutl -verify
com a chave pública.
Modo avaliação para laboratório e pré-teste. Numa organização listada em
BIOMETRICS_EVALUATION_ORG_IDS, a página mostra um selo com a sessão e a tentativa
e, ao fim, o resultado com os motivos, para o avaliador registrar cada
apresentação. Fora dessas organizações a página nunca mostra o resultado.

Medir antes de ligar em produção. POST /invites gera um link válido por até
90 dias para voluntários. A página rotula o que a pessoa mostra à câmera — ela
mesma, foto impressa, foto numa tela, vídeo numa tela ou máscara, as espécies da
ISO/IEC 30107-3 — e o relatório devolve APCER por espécie, BPCER e as taxas de
não resposta, no vocabulário do laudo.

Integração com outros building blocks
| Bloco | Como se encaixa |
|---|---|
| File Storage | Guarda o vídeo do desafio, o melhor quadro e o documento de referência, pela fachada IFileStorageFacade. Falha de armazenamento não perde a verificação: o hash fica |
| Billing | Sessão avaliada vira UsageEvent no medidor biometrics.verification, pela fachada IBillingFacade |
| Webhooks Engine | Entrega os eventos abaixo a quem assinou |
| Customers | customerId na sessão e no cadastro facial liga a verificação ao cliente |
| IAM | Escopo por organização e as permissões BIOMETRICS_* |
Eventos publicados
| Evento | Quando |
|---|---|
biometrics.session.created | Sessão aberta |
biometrics.session.completed | Decisão tomada, com o status |
biometrics.session.review_required | INCONCLUSIVE: alguém precisa olhar |
biometrics.session.expired | Sessão venceu sem decisão |
biometrics.subject.locked | Criação recusada por excesso de falhas do titular |
biometrics.subject.erased | Dados de um titular apagados a pedido |
biometrics.enrollment.created e .revoked | Cadastro facial gravado ou revogado |
Configuração e operação
Variáveis de ambiente
| Variável | Obrigatória | Padrão | Descrição |
|---|---|---|---|
BIOMETRICS_CREDENTIAL_MASTER_KEY | sim | — | 64 hex. Cifra a credencial do fornecedor e a chave de assinatura |
BIOMETRICS_TEMPLATE_MASTER_KEY | sim | — | 64 hex, diferente da anterior. Cifra o cadastro facial |
BIOMETRICS_TEMPLATE_MASTER_KEY_PREVIOUS | não | — | A chave antiga durante a rotação, até o rekey terminar |
BIOMETRICS_ENGINE_URL | não | http://face-engine:8000 | Endereço interno do motor |
BIOMETRICS_ENGINE_TOKEN | sim com o motor | — | Bearer compartilhado com o motor |
BIOMETRICS_PUBLIC_URL | sim | — | Base pública do bloco, usada para montar o captureUrl |
BIOMETRICS_EXPIRE_SCHEDULER_ENABLED | não | true | Sessão vencida vira EXPIRED a cada minuto |
BIOMETRICS_RETENTION_PURGE_ENABLED | não | false | Purga de mídia por retention.mediaDays. Desligada por padrão: apagar é irreversível |
BIOMETRICS_RETENTION_PURGE_INTERVAL_HOURS | não | 24 | Cadência da purga |
BIOMETRICS_USAGE_CONSUMER_ENABLED | não | false | Liga o faturamento |
BIOMETRICS_EVALUATION_ORG_IDS | não | vazio | Organizações de avaliação (laboratório, pré-teste), separadas por vírgula. Só nelas a página de captura mostra o resultado |
As duas master keys são separadas de propósito: a da credencial gira com o contrato do fornecedor; a do cadastro facial exige recifrar a base inteira.
Política por organização, sem deploy (PUT /policy)
| Chave | Padrão | Para quê |
|---|---|---|
minAssurance | ENHANCED | Garantia mínima do driver. O MOCK é BASIC e é recusado |
thresholds.match | recusa < 0,55, aprova ≥ 0,70 | Entre os dois é zona cinzenta, INCONCLUSIVE |
thresholds.livenessPassive | recusa < 0,40, aprova ≥ 0,80 | Liveness passivo |
maxAttempts | 3 | Tentativas por sessão |
sessionTtlMinutes | 15 | Validade do link, até 60 |
challenge | 3 gestos, 4,5 a 6,5 s cada, até 22 s | Quantidade, janelas e catálogo |
document | tudo desligado | Recusar documento vencido; exigir MRZ ou QR válido |
lockout | 5 falhas em 60 min → 30 min | Bloqueio por titular |
retention | mídia por 30 dias | E validade do cadastro facial, se houver |
onInconclusive | REVIEW | Ou REJECT |
allowedCaptureChannels | HOSTED, IFRAME, SDK | Por onde a câmera pode abrir |
Observabilidade. O bloco publica métricas OpenTelemetry de baixa cardinalidade: sessões por fluxo e resultado, checks por tipo, histograma de score, motivos da decisão, duração da avaliação e de cada etapa do motor, e criações recusadas. Nenhum rótulo carrega CPF, sessão ou organização.
O motor em operação. O face-engine sobe com os pesos embutidos na imagem e
aquece os modelos antes de se declarar saudável. Uma sessão de celular leva ~8 s
em 4 vCPUs; a análise dos pontos do rosto roda em paralelo por quadro.
Segurança e compliance
Biometria é dado pessoal sensível (LGPD art. 5º, II). O desenho parte disso:
| Tema | Como |
|---|---|
| Finalidade | purpose obrigatório em toda sessão, com legalBasis opcional, gravados na trilha |
| Titular | CPF em HMAC e máscara, nunca em claro |
| Cadastro facial | Cifrado com chave própria, nunca devolvido pela API, com rotação por rekey |
| Mídia | Retenção configurável; a purga apaga o vídeo e preserva o hash |
| Direitos do titular | subjects/report diz o que existe; subjects/erase apaga sessões, mídia e cadastros de um CPF |
| Decisão automatizada | INCONCLUSIVE vai para revisão humana (art. 20) |
Quem está diante da câmera não carrega credencial. A página recebe um
captureToken de escopo único e uso único por tentativa, e só as rotas de captura
o aceitam. A página roda com CSP por nonce; os arquivos do guia ao vivo são
servidos pelo próprio bloco com hash fixo, sem CDN.
A página não diz o resultado. Nem aprovado, nem reprovado: quem decide o que dizer à pessoa é a empresa, depois de ler o envelope pelo servidor. Isso impede que um fraudador use a página como oráculo para ajustar o ataque.
Contra injeção de vídeo. Os gestos são sorteados em quantidade, ordem e
duração por sessão, e a janela de cada um é conferida no vídeo. Na medição, um
vídeo genuíno aprovado, reinjetado por câmera virtual, passou no liveness passivo
e na continuidade — é rosto real — e foi reprovado pela janela sorteada. Troca de
dispositivo entre tentativas vira INCONCLUSIVE.
Contra força bruta. O mesmo CPF com 5 sessões reprovadas ou inconclusivas em 60 minutos fica bloqueado por 30. Os três números são da política.
Evidência assinada. Cada organização tem uma chave Ed25519 própria, criada no primeiro uso, com a privada cifrada. A pública é publicada para que a auditoria verifique sem depender da Catalisa.
Conformidade com normas
O bloco é desenvolvido em conformidade com os requisitos e as recomendações das normas abaixo. Conformidade declarada pela Catalisa — sem homologação nem certificação de terceiro.
| Norma | Onde o bloco aplica |
|---|---|
| ISO/IEC 30107-1 e 30107-3 | Detecção de ataque de apresentação (PAD): famílias de ataque, métricas APCER e BPCER e protocolo de avaliação |
| ISO/IEC 19795-1 | Métricas de desempenho do match, FMR e FNMR |
| ICAO Doc 9303 | Leitura da MRZ do passaporte, com validação dos dígitos verificadores |
| LGPD — arts. 5º II, 11, 18 e 20 | Dado sensível, base legal, direitos do titular e revisão de decisão automatizada |
| Circular BCB nº 3.978/2020 | Apoia a identificação e a qualificação de clientes da política de PLD/FT |
| RFC 8032 — Ed25519 | Assinatura da evidência |
| NIST SP 800-38D — AES-256-GCM | Cifra do cadastro facial e das credenciais |
| RFC 7519 — JWT | Token de captura de uso único |
| W3C CSP Level 3 e Permissions Policy | Isolamento da página de captura e acesso à câmera |
| OpenTelemetry | Métricas de operação |
Certificações no roadmap (nenhuma emitida até aqui)
| Certificação | Escopo |
|---|---|
| iBeta ISO/IEC 30107-3 Nível 1 | Ataques com foto impressa, tela, vídeo e máscara de papel |
| iBeta ISO/IEC 30107-3 Nível 2 | Máscaras 3D de látex, silicone e resina |
| FIDO Alliance Face Verification | Desempenho do match, PAD e injeção, com laboratório credenciado |
| CEN/TS 18099 | Detecção de injeção de dados biométricos, como câmera virtual e vídeo sintético |
Limitações conhecidas
| Escopo | Como o bloco trata |
|---|---|
| Autenticidade física do documento | Fora do escopo. O bloco lê MRZ e QR para validade e consistência; documentoscopia é de provedor especializado |
| Reconhecimento 1:N | Fora do escopo. O fluxo DEDUP está reservado no contrato |
| FaceTec, Unico e Serpro Datavalid | Declarados no catálogo; ativados com a credencial do contrato de cada um |
| Certificação de laboratório | Entregue pelos drivers CERTIFIED |
| Duração do vídeo | Até 24 segundos por tentativa |
| Dispositivo recomendado | Celular, pela câmera e pela luz. O gesto de aproximar o rosto (APPROACH) é indicado só para celular |
| Atestação de dispositivo | Roadmap: SDK nativo com atestação na origem da captura |
Perguntas frequentes
Usa inteligência artificial de terceiros?
Usa modelos de pesos abertos publicados por terceiros — YuNet, MiniFASNet, MediaPipe, Facenet512 —, rodando na nossa infraestrutura. Nenhuma imagem sai para serviço externo. Os pesos são fixados por hash e a versão aparece em cada check.
Substitui a FaceTec?
O motor próprio atende sem contrato de terceiro nos fluxos em que o nível
ENHANCED basta. Onde a certificação for exigida, a FaceTec entra como driver e a
integração não muda.
Por que a página não mostra se aprovou?
Porque a página está nas mãos de quem está diante da câmera, que pode ser um fraudador. Mostrar o resultado transforma a página num oráculo para ajustar o ataque. A empresa lê o envelope pelo servidor e decide o que dizer.
A empresa pode configurar quanto tempo cada gesto dura?
Pode, pela política da organização, sem deploy: quantidade de gestos, janela mínima e máxima de cada um, o tempo total até 24 segundos e quais gestos entram no sorteio.
Qual a diferença entre REJECTED e INCONCLUSIVE?
REJECTED é o sistema seguro de que não deve aprovar. INCONCLUSIVE é zona
cinzenta — match entre os limiares, sinal heurístico de câmera virtual, troca de
dispositivo — e vai para revisão humana, como pede a LGPD para decisão
automatizada com efeito relevante.
Precisa pedir o documento em toda verificação?
Não. O documento entra uma vez, no ONBOARDING com enrollOnApprove. Depois, a
autenticação compara com o cadastro facial, que chega a 0,95 de match.
Funciona no navegador ou precisa de app?
No navegador, sem instalar nada. Antes de gravar, a página mostra o primeiro
gesto com uma contagem de 2,5 s. O guia ao vivo roda no próprio navegador e se
desliga sozinho em conexão lenta ou com economia de dados; a captura funciona sem
ele. Para app nativo, a rota POST /sessions/:id/captures aceita a submissão de
um SDK próprio com o mesmo captureToken.