NovoVerificação de identidade facial

Como verificar identidade com documento e selfie via API

Tutorial para criar um link de verificação de identidade com selfie e documento, receber a decisão por webhook assinado e tratar a revisão manual com a API da CPFHub.io.

Redação CPFHub.io
Redação CPFHub.io
··10 min de leitura
Como verificar identidade com documento e selfie via API

Introdução

Para verificar a identidade de uma pessoa com documento e selfie via API, você cria um link de verificação com uma chamada POST, envia a URL para a pessoa e recebe a decisão (approved, manual_review ou rejected) em um webhook assinado. A captura da selfie, a leitura do documento e a comparação facial acontecem em uma página hospedada pela CPFHub.io, então você não precisa construir nem manter essa interface.

A Verificação de Identidade da CPFHub.io faz a prova de vida, lê o documento (CNH, RG ou passaporte), compara o rosto da selfie com a foto do documento e cruza os dados com o CPF. Ela está liberada em todos os planos, inclusive no Grátis.

Neste tutorial, vamos criar o link, entender a decisão devolvida, validar o webhook com segurança e organizar o fluxo de revisão manual. Os exemplos usam os campos da documentação oficial.


Como o fluxo funciona

O fluxo tem três passos e o seu backend participa de dois deles:

  1. POST /kyc/links cria o link e devolve a url.
  2. Você envia a url para a pessoa ou redireciona para ela no seu cadastro.
  3. A decisão chega no seu webhook, ou pode ser consultada com GET /kyc/links/{id}.
  • Autenticação: o header x-api-key, a mesma chave das consultas de CPF.

  • Custo: 5 créditos por verificação concluída, do mesmo saldo das consultas. Criar e consultar links não custa nada, e falha técnica da CPFHub.io não cobra.

  • O que não faz: a verificação não valida o documento em base do governo, não faz perícia documental e não aceita CNH Digital.


O único cabeçalho obrigatório é x-api-key. Os campos do body são todos opcionais:

CampoPara que serve
externalIdO seu identificador do cadastro (até 200 caracteres). Volta no webhook e permite ligar a decisão ao usuário certo.
redirectUrlPara onde a pessoa volta ao terminar. Só https.
expiresInMinutesValidade, de 5 a 10080. Padrão: 1440 (24 h).
maxAttemptsTentativas, de 1 a 10. Padrão: 3.

Exemplo com cURL

curl -X POST "https://api.cpfhub.io/kyc/links" \
  --max-time 30 \
  -H "x-api-key: SUA_CHAVE_DE_API" \
  -H "content-type: application/json" \
  -d '{
    "externalId": "cliente-4821",
    "redirectUrl": "https://seusite.com.br/cadastro/concluido"
  }'

Exemplo em TypeScript com timeout

const res = await fetch('https://api.cpfhub.io/kyc/links', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.CPFHUB_API_KEY!,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    externalId: 'cliente-4821',
    redirectUrl: 'https://seusite.com.br/cadastro/concluido',
  }),
  signal: AbortSignal.timeout(30_000),
})
const body = await res.json()

if (!res.ok) {
  // error vem como string ou como objeto { message }
  const message = typeof body.error === 'string' ? body.error : body.error?.message
  throw new Error(`CPFHub.io ${res.status}: ${message}`)
}

console.log(body.data.url) // envie para a pessoa

Resposta

{
  "success": true,
  "data": {
    "id": "5b1d7c2e-8a4f-4e7b-9c3d-2f6a1e0b9d47",
    "url": "https://verificar.cpfhub.io/AbC123xYz",
    "externalId": "cliente-4821",
    "expiresAt": "2026-11-06T14:00:00.000Z",
    "maxAttempts": 3,
    "language": null
  }
}

Guarde o id junto do cadastro: ele volta no webhook como linkId. Crie um link por pessoa. Quem tiver a url pode fazer a verificação no lugar dela, então envie o link só para o destinatário e use a validade e o número de tentativas mais curtos que o seu fluxo permitir.


Passo 2: entender a decisão

Ao terminar, a pessoa volta para o redirectUrl com kyc_link_id e external_id na URL, mas sem a decisão. A decisão vem de uma destas duas fontes, e só dessas.

Consulta pela API

GET /kyc/links/{id} devolve o estado do link e, quando a pessoa já terminou, o objeto verification. O status do link pode ser pending, completed, expired ou canceled. A url só volta enquanto o link pode ser usado, e verification é null até a pessoa terminar. A resposta não traz dados pessoais.

Os três resultados

verification.statusSignificadoO que fazer
approvedTudo certo. O link é encerrado.Libere o cadastro.
manual_reviewPrecisa de uma pessoa da sua equipe. O link é encerrado.Revise no painel (veja a seção sobre revisão manual).
rejectedReprovada pelas regras da conta.A pessoa pode tentar de novo enquanto houver tentativas.

Os motivos chegam em reasons, no formato [{ "code", "message" }]. Trate um code desconhecido como caso de revisão e mostre o message apenas para a sua equipe, nunca para o usuário final.


Passo 3: receber a decisão por webhook

O webhook evita que você precise consultar a API em loop. Cada decisão, automática ou tomada pela sua equipe no painel, gera um POST para a URL cadastrada. O evento não traz dados pessoais: só IDs, a decisão e os motivos.

Configuração

No painel, em Verificações > Configurações > Integrações:

  1. Cadastre a URL do seu endpoint. Só https e só endereços públicos.
  2. Copie o segredo e guarde em uma variável de ambiente, como CPFHUB_WEBHOOK_SECRET.
  3. Use o envio de teste para receber um evento kyc.test e conferir a assinatura.

Formato do evento

{
  "event": "kyc.decision",
  "eventId": "3e8a1f20-5c4b-4d9e-8f7a-6b2c1d0e9f8a",
  "verificationId": "8f3c2a1e-77b0-4d3c-9a1f-0e5b6c7d8e9f",
  "linkId": "5b1d7c2e-8a4f-4e7b-9c3d-2f6a1e0b9d47",
  "externalId": "cliente-4821",
  "status": "approved",
  "previousStatus": null,
  "decidedBy": "automatic",
  "reasons": [],
  "occurredAt": "2026-11-05T14:06:12.000Z"
}

A requisição também traz os headers x-cpfhub-event, x-cpfhub-delivery (use para ignorar repetições) e x-cpfhub-signature, no formato t=<unix>,v1=<hex>.

Verificando a assinatura

v1 é o HMAC-SHA256, em hexadecimal, de "<t>.<corpo cru>" calculado com o seu segredo. Três cuidados evitam a maior parte dos erros: calcular sobre o corpo exatamente como chegou, antes de qualquer parse de JSON; comparar em tempo constante; e recusar eventos com t a mais de 5 minutos do seu relógio.

import crypto from 'node:crypto'
import express from 'express'

const app = express()

// Corpo cru: a assinatura é calculada sobre os bytes recebidos
app.post('/webhooks/cpfhub', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('x-cpfhub-signature') ?? ''
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  const expected = crypto
    .createHmac('sha256', process.env.CPFHUB_WEBHOOK_SECRET!)
    .update(`${parts.t}.${req.body}`)
    .digest()
  const received = Buffer.from(parts.v1 ?? '', 'hex')

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(received, expected) &&
    Math.abs(Date.now() / 1000 - t) < 300
  if (!valid) return res.sendStatus(401)

  const event = JSON.parse(req.body.toString())
  if (event.event === 'kyc.decision') {
    // Ligue a decisão ao seu cadastro pelo externalId
    console.log(event.externalId, event.status)
  }
  res.sendStatus(200)
})

Entrega e retentativas

  • Confirmação: responda com qualquer 2xx em até 10 segundos e processe o evento depois, em uma fila.

  • Retentativas: sem 2xx, a CPFHub.io tenta de novo em 1 min, 5 min, 30 min, 2 h e 12 h. Depois disso a entrega fica como falha e pode ser reenviada pelo painel.

  • Repetições: o mesmo evento pode chegar mais de uma vez. Ignore um x-cpfhub-delivery que você já processou.

  • Ordem: não há garantia de ordem entre eventos. Use occurredAt e, na dúvida, confirme a decisão atual com GET /kyc/links/{id}.

Nunca libere o cadastro com base apenas no redirecionamento. A pessoa pode abrir a URL de retorno sozinha, então só um webhook com assinatura válida, ou a consulta pela API, prova a decisão. Mais detalhes em Webhook da Verificação de Identidade.


Passo 4: tratar a revisão manual

Nem toda verificação é um sim ou um não. Quando o rosto fica em uma zona de dúvida ou o CPF informado diverge do documento, a decisão é manual_review, e uma pessoa da sua equipe precisa olhar o caso no painel.

Um fluxo simples para a equipe:

  1. Ao receber manual_review, marque o cadastro como "em análise" usando o externalId e não libere o acesso.
  2. Abra o caso no painel, em Verificações, e leia os reasons para saber o que conferir.
  3. Aprove ou reprove no painel. Essa decisão gera um novo webhook com decidedBy: "manual" e previousStatus: "manual_review".
  4. No seu backend, trate esse segundo evento como a decisão final do cadastro.

Os limites de semelhança facial, os documentos aceitos, o tratamento de documento vencido e a retenção ficam em Verificações > Configurações e valem para todos os links da conta. Ajustar esses limites muda a proporção entre aprovações automáticas e casos enviados para a equipe.


Tratando erros

StatusQuando aconteceO que fazer
400Parâmetro inválido (redirectUrl sem https, valor fora do permitido)Corrija o body. Não repita.
401API Key ausente, inválida ou inativaConfira o header x-api-key.
403Conta inativa ou bloqueadaFale com o suporte.
404Link não existe ou é de outra API KeyConfira o id.
503Falha passageiraRepita com espera crescente (2 s, 4 s, 8 s).

O formato dos erros segue o padrão descrito em Códigos de Erro. Como error pode vir como string ou como objeto com message, o exemplo em TypeScript acima já trata os dois formatos.


Perguntas frequentes

Quanto custa cada verificação?

São 5 créditos por verificação concluída, do mesmo saldo das consultas de CPF, inclusive quando a pessoa é reprovada. Criar e consultar links não consome créditos, e falha técnica da CPFHub.io não cobra.

Posso usar no plano Grátis?

Sim. A verificação está liberada em todos os planos, inclusive no Grátis, que tem 50 créditos por mês. Cada verificação concluída consome 5 desses créditos.

Posso saber o resultado só pelo redirecionamento?

Não. O redirecionamento traz apenas kyc_link_id e external_id, sem a decisão, e a pessoa pode abrir a URL de retorno sozinha. Confirme sempre pelo webhook assinado ou por GET /kyc/links/{id}.

O que acontece se meu endpoint ficar fora do ar?

A CPFHub.io repete a entrega em 1 min, 5 min, 30 min, 2 h e 12 h. Depois disso a entrega fica como falha e pode ser reenviada pelo painel. Como não há garantia de ordem, consulte a API quando precisar da decisão atual.

A verificação aceita CNH Digital?

Não. Os documentos aceitos são CNH, RG e passaporte, e os tipos permitidos para a sua conta são configurados no painel.



Conclusão

Verificar identidade com documento e selfie via API se resume a quatro ações: criar um link por pessoa, guardar o id e o externalId, validar a assinatura do webhook e dar à sua equipe um caminho claro para os casos em manual_review. A CPFHub.io cuida da captura, da prova de vida, da leitura do documento e da comparação facial.

Para começar, crie sua conta em app.cpfhub.io, gere sua API Key e consulte a documentação da Verificação de Identidade.

CPFHub.io

Pronto para integrar a API?

50 créditos gratuitos para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

WhatsApp