Documentação UNIFOKAL
Verificação de identidade ponta a ponta, sem call de vendas: você cria um Flow, instala o Widget e recebe o resultado por Webhook. A captura e o processamento (CPF, OCR, face match, liveness) rodam no UNIFOKAL, então o seu front nunca toca foto, selfie ou documento e você fica fora do escopo mais pesado da LGPD.
flow_id.flow_id + reference_id) → recebe o id; no front, IdSaas.mount({ sessionId }). Nenhum segredo no navegador.verification.completed com status e score.flow | Receita de verificação: quais módulos rodam e em que ordem. |
verification | Uma execução do flow por um usuário. Tem status e score. |
reference_id | O ID do usuário no seu sistema. Você envia na criação da sessão (opcional) e recebe igual no webhook e na API. |
livemode | false em sandbox, true em produção. Vem em toda verificação e webhook. |
O Widget
São 2 passos. No backend, você cria a sessão do usuário no Flow, enviando o seu reference_id (o id do usuário no seu sistema; opcional, mas recomendado: ele volta no webhook e na API para você correlacionar). A resposta traz o id da sessão. No front, você monta o widget com esse id. Nenhum segredo vai ao navegador (o único segredo é a sua sk_, que fica no seu backend). O ambiente (sandbox × produção) é definido pela chave secreta usada na criação da sessão. Os módulos são os do Flow. Você não os declara no widget.
// 1) BACKEND: crie a sessão do usuário no Flow (a sk_ fica AQUI, nunca no navegador)
POST /v1/verification-sessions
Authorization: Bearer sk_live_…
{ "flow_id": "flow_a1b2c3", "reference_id": "usr_8842" }
→ { "id": "vs_…" } // só o sessionId
// 2) FRONT: monte o widget com SÓ o sessionId (é a credencial; nenhum segredo)
// URL imutável por versão + integrity (SRI): o navegador recusa o bundle se ele for trocado.
// O hash sha384 de cada versão é publicado nas notas de release.
<script
src="https://cdn.idsaas.com/widget/0.1.0/widget.global.js"
integrity="sha384-4fwTioy2FQGrTNEQ5fubGBC1DcKrVC48ncvm16dyJtr8Xu+O4NLQfe0JpHlziJgp"
crossorigin="anonymous"></script>
<div id="identity"></div>
<script>
IdSaas.mount({ sessionId: "vs_…", container: "#identity" })
</script>| campo | tipo | descrição |
|---|---|---|
| sessionId | obrigatório | ID da sessão criada no backend (vs_…). É a credencial do widget: nenhum segredo na página. |
| container | opcional | Seletor CSS onde o widget monta (default: #idsaas-widget). |
| baseUrl | opcional | Origem da API UNIFOKAL (default: a origem de onde o widget é servido). |
Autenticação
Chamadas REST usam Bearer token no header. A chave secreta define o ambiente: sk_test roda em sandbox, sk_live em produção. Nunca exponha a secret no front: ela é só server-side.
curl https://api.idsaas.com/v1/flows \ -H "Authorization: Bearer sk_live_…"
401. Chave de teste contra produção → 403 (veja Sandbox).Segurança do fluxo
O front recebe do seu backend apenas o sessionId (vs_), escopado a UMA única sessão (uso curto, com TTL). Ele é a credencial do widget: nenhum segredo vai ao navegador. Não é uma chave global: só habilita a captura daquela verificação, então expor por engano não compromete outras sessões nem a sua conta. A sk_ (secret), usada no backend para criar a sessão, é a única credencial sensível e nunca sai do servidor. A captura e o processamento acontecem no UNIFOKAL; o resultado oficial só trafega no webhook assinado para o seu backend.
API REST (3 endpoints públicos)
A superfície server-to-server é enxuta de propósito: com a sk_ você cria a sessão e cuida da confiabilidade do webhook. Todo o resto (flows, verificações, blocklist, destinos de webhook, billing) se gerencia pela plataforma.
POST /v1/verification-sessions
Authorization: Bearer sk_live_…
{ "flow_id": "flow_a1b2c3", "reference_id": "user_123" }
→ 201 { "id": "vs_01J8…", "status": "requires_input", "expires_at": "…" }
// monte o widget com esse id; o resultado chega no seu webhookGET /v1/webhook-events?page=1&limit=20
Authorization: Bearer sk_live_…
→ 200 { "data": [ { "event_type": "completed",
"verification_id": "ver_…", "reference_id": "user_123",
"target_url": "https://sua.api/webhooks", "status": "error",
"status_code": null, "attempts": 3,
"failed_at": "2026-07-28T21:14:09.312Z" } ],
"page": 1, "totalPages": 1, "totalItems": 1 }
// UMA entrada por verificação+evento (idempotente): reemissões não duplicam;
// attempts acumula o total de falhas. Ciclo: até 3 tentativas ("sending") →
// 2xx marca "success"; esgotou sem 2xx (4xx/5xx ou sem resposta) marca "error"
// e entra NESTA lista. Entregue com sucesso (original OU replay) sai da lista.
// failed_at = quando a última tentativa falhou (a data do erro).
// target_url = o destino ATUAL ligado ao flow (se você trocou o webhook, já vem o novo).
// limit: máximo 20 por página (pagine com page=2, 3…)POST /v1/webhook-events/replay
Authorization: Bearer sk_live_…
{ "verification_ids": ["ver_aaa…", "ver_bbb…"] } // mínimo 1, máximo 20 por chamada
→ 200 { "results": [
{ "verification_id": "ver_aaa…", "resent": true, "http_status": 200 },
{ "verification_id": "ver_bbb…", "resent": false, "http_status": null,
"error": "nothing_to_replay" } ],
"resent": 1, "failed": 1 }
// cada item reenvia os webhooks com erro daquela verificação: o corpo EXATO salvo
// (assinatura nova) no destino ATUAL do flow (trocou o endpoint? o replay já
// dispara no novo). Reenviou com sucesso? A verificação sai da listagem de erros.
// Erro de um item não derruba os demais. Rate limit: 30 chamadas/min por conta.GET /v1/webhook-events e redispare pelos verification_ids: você recebe exatamente o mesmo corpo que teria recebido, sem nenhuma mudança.Webhooks & eventos
A verdade é o webhook: o resultado oficial vai ao seu backend por evento assinado. Os principais são verification.completed e verification.blocked. O ciclo de até 3 tentativas cobre soluços momentâneos do seu endpoint, sem prender a resposta ao usuário.
POST https://seu-backend.com/webhooks/unifokal
X-IDSAAS-Signature: t=...,v1=...
{
"id": "evt_ver_…_completed",
"type": "verification.completed",
"livemode": true,
"data": {
"verification_id": "ver_…", "reference_id": "usr_8842",
"status": "approved", "score": 95,
"risk_level": "low", "recommendation": "approve",
"checks": { "identity": "pass", "liveness": 0.94, "cpf": "valid" },
"check_details": [
{ "module": "cpf_ocr", "passed": true, "outcome": "approved", "score": 95,
"data": { "name": "João Silva", "cpf": "123.456.789-00",
"document": { "type": "cnh", "number": "07969013668" } } },
{ "module": "cpf", "passed": true, "outcome": "approved", "score": 95,
"data": { "nome": "JOÃO SILVA", "telefones": ["11999999999"],
"emails": ["joao@exemplo.com"] } }
]
}
}verification.blocked sai quando o documento está na sua blocklist e não é cobrado.
Dado cadastral só com identidade comprovada. Os módulos de Validação CPF exigem Verificação de Identidade + Face Match + Liveness no flow, e o dado oficial do titular só é entregue quando a biometria aprova e a leitura do documento (OCR) aprova: se o Face Match, o Liveness ou a leitura do documento reprovarem, o webhook sai sem o blocodata desses módulos e você não paga por eles. Além disso, cada conta tem um teto diário de consultas cadastrais externas: acima do teto, o módulo fica pendente e também não é cobrado. Precisa de um teto maior? Fale com o suporte.
Valide a assinatura do webhook
A URL do seu webhook é alcançável por qualquer um na internet: sem validação, um atacante que descubra o endereço pode forjar um POST com "status": "approved" e o seu sistema aprovaria quem não deveria. Por isso todo evento sai assinado, e o seu backend deve validar antes de confiar no corpo.
Como funciona: cada POST leva o header X-IDSAAS-Signature: t=<timestamp>, v1=<hmac>. O v1 é um HMAC-SHA256 de {timestamp}.{corpo cru} calculado com o seu segredo whsec_ (exibido uma única vez ao criar o endpoint na plataforma; dá para rotacionar quando quiser). Só quem tem o segredo consegue produzir a assinatura: se ela bater, o evento veio do UNIFOKAL.
import crypto from "node:crypto";
// 1) separe t (timestamp) e v1 (assinatura) do header
const [t, v1] = parseHeader(req.headers["x-idsaas-signature"]);
// 2) recalcule o HMAC do mesmo jeito: segredo whsec_ sobre "timestamp.corpo"
// ⚠ use o corpo CRU (bytes recebidos), nunca o JSON re-serializado
const expected = crypto.createHmac("sha256", SIGNING_SECRET)
.update(`${t}.${rawBody}`).digest("hex");
// 3) compare em tempo constante (=== vazaria a assinatura por timing attack)
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)))
return res.status(400).end(); // assinatura inválida: descarte o corpo
// 4) anti-replay: rejeite eventos velhos (t entra no material assinado)
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(400).end();Responda 2xx só depois da validação. Reenvios (retry e replay) chegam com assinatura e t novos sobre o mesmo corpo, então a validação continua passando.
Reemissão manual (painel): quando o dono aprova ou recusa manualmente uma verificação, o webhook é reenviado com um id de evento novo e o corpo é o original com apenas os campos de decisão sobrescritos (status, recommendation, risk_level, decision_reason); trate como upsert pelo verification_id. Replay self-service: se o seu sistema ficou fora do ar, use GET /v1/webhook-events + replay (em lote, até 20 por chamada) para receber o corpo exato de novo.
Lista de bloqueio
A lista é da sua conta e se gerencia pela plataforma (aba Lista de bloqueio): marque CPF/CNPJ suspeitos e novas verificações com eles voltam blocked (evento verification.blocked) e não são cobradas. O número é guardado com hash e criptografia; nunca volta em texto puro.
Sandbox
O sandbox roda o pipeline completo, grátis e com teto mensal (livemode:false). CPFs determinísticos disparam cada resultado para você testar o webhook sem capturar nada:
000.000.000-00 → approved 000.000.000-01 → pending 000.000.000-02 → denied (reprovado) qualquer outro → approved
Privacidade e consentimento
Antes de qualquer captura, o widget mostra à pessoa um aviso automático sobre o tratamento dos dados (o que é coletado, por quanto tempo é guardado e que as imagens não treinam inteligência artificial). Você não precisa passar versão de termos nem registrar consentimento no seu código: a versão vigente é definida pelo servidor e o consentimento fica registrado automaticamente, uma vez por sessão.
Leia o texto que a pessoa vê em Aviso ao Usuário Final. Como Cliente, o uso da plataforma é regido pelos Termos de Uso e pela Política de Privacidade.
Pronto para integrar? Crie sua conta grátis e gere uma chave de sandbox em minutos.