# 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.

**Publicado:** 05/11/2026
**Autor:** Redação CPFHub.io
**URL:** https://www.cpfhub.io/blog/como-verificar-identidade-documento-selfie-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**](https://www.cpfhub.io/), então você não precisa construir nem manter essa interface.

A [Verificação de Identidade da CPFHub.io](https://www.cpfhub.io/verificacao-de-identidade) 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](https://www.cpfhub.io/documentacao/referencia/verificacao-de-identidade).

---

## 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.

---

## Passo 1: criar o link de verificação

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

| Campo | Para que serve |
| --- | --- |
| `externalId` | O seu identificador do cadastro (até 200 caracteres). Volta no webhook e permite ligar a decisão ao usuário certo. |
| `redirectUrl` | Para onde a pessoa volta ao terminar. Só `https`. |
| `expiresInMinutes` | Validade, de `5` a `10080`. Padrão: `1440` (24 h). |
| `maxAttempts` | Tentativas, de `1` a `10`. Padrão: `3`. |

### Exemplo com cURL

```bash
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

```typescript
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

```json
{
  "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.status` | Significado | O que fazer |
| --- | --- | --- |
| `approved` | Tudo certo. O link é encerrado. | Libere o cadastro. |
| `manual_review` | Precisa de uma pessoa da sua equipe. O link é encerrado. | Revise no painel (veja a seção sobre revisão manual). |
| `rejected` | Reprovada 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](https://app.cpfhub.io), 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

```json
{
  "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.

```typescript
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](https://www.cpfhub.io/documentacao/referencia/verificacao-de-identidade-webhook).

---

## 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](https://app.cpfhub.io), 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

| Status | Quando acontece | O que fazer |
| --- | --- | --- |
| `400` | Parâmetro inválido (`redirectUrl` sem `https`, valor fora do permitido) | Corrija o body. Não repita. |
| `401` | API Key ausente, inválida ou inativa | Confira o header `x-api-key`. |
| `403` | Conta inativa ou bloqueada | Fale com o suporte. |
| `404` | Link não existe ou é de outra API Key | Confira o `id`. |
| `503` | Falha passageira | Repita com espera crescente (2 s, 4 s, 8 s). |

O formato dos erros segue o padrão descrito em [Códigos de Erro](https://www.cpfhub.io/documentacao/referencia/codigos). 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.

---

### Leia também

*   [Anatel, biometria facial e prova de vida em linhas de celular](https://cpfhub.io/blog/anatel-biometria-facial-prova-de-vida-linhas-celular)
*   [Convergência entre biometria e CPF: o futuro da identidade multimodal no Brasil](https://cpfhub.io/blog/convergencia-biometrica-e-cpf-o-futuro-da-identidade-multimodal-no-brasil)
*   [KYC no Brasil: quais setores são obrigados a validar CPF por lei](https://cpfhub.io/blog/kyc-no-brasil-quais-setores-sao-obrigados-a-validar-cpf-por-lei)
*   [Tendências de automação de KYC e o futuro da validação de CPF para fintechs](https://cpfhub.io/blog/tendencias-automacao-kyc-futuro-validacao-cpf-fintechs)

---

## 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**](https://www.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](https://app.cpfhub.io), gere sua API Key e consulte a [documentação da Verificação de Identidade](https://www.cpfhub.io/documentacao/referencia/verificacao-de-identidade).

