> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vu-one.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks do Identity Link: Eventos do Ciclo de Verificação

> Receba notificações em tempo real no seu servidor para cada etapa do processo de verificação de identidade com Identity Link.

Os webhooks permitem que o seu backend receba notificações HTTP em tempo real enquanto uma pessoa avança por uma verificação do Identity Link. Em vez de consultar a API em laço, você registra uma URL e a VU publica um payload JSON assinado sempre que uma etapa é concluída ou falha.

<Note>
  Webhooks fazem parte da API, então exigem plano **Premium ou Pro** — o mesmo requisito de `POST /oauth/token`. Veja [Planos](/pt/identity-link/planos).
</Note>

## Registrar um endpoint

```bash theme={null}
curl -X POST https://api.identity.vu-one.com/api/v1/webhook-endpoints \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seusite.com/webhooks/vu",
    "description": "Eventos de verificação em produção"
  }'
```

O corpo aceita exatamente dois campos: `url` (obrigatório) e `description` (opcional). Qualquer outro campo é rejeitado. A chamada responde `201`:

```json theme={null}
{
  "_id": "b6b0a1f2-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
  "userId": "9f8e7d6c-5b4a-4938-2716-0f5e4d3c2b1a",
  "description": "Eventos de verificação em produção",
  "url": "https://seusite.com/webhooks/vu",
  "createdAt": "2026-08-30T14:02:11.000Z",
  "updatedAt": "2026-08-30T14:02:11.000Z"
}
```

<Warning>
  **A resposta não traz segredo algum.** A assinatura não é por endpoint: existe um único segredo de assinatura por conta, e todos os endpoints registrados recebem payloads assinados com ele.
</Warning>

## Onde fica o segredo de assinatura

O seu `webhookSecret` é um valor de 256 bits exibido no aplicativo em **Configurações → Desenvolvedores**, ao lado do Client Id e do Client Secret. O mesmo valor volta em um `GET /users/me` autenticado.

Para rotacioná-lo, use **Atualizar** nessa tela ou chame:

```bash theme={null}
curl -X PATCH https://api.identity.vu-one.com/api/v1/users/me/webhook-secret \
  -H "Authorization: Bearer <token>"
```

A resposta traz o novo segredo. A rotação vale imediatamente, então publique o novo valor no seu validador antes de rotacionar, ou aceite uma janela curta de entregas rejeitadas.

## Eventos

Doze eventos cobrem o ciclo de vida. `started` dispara quando a etapa começa, `successful` quando ela passa e `failed` quando ela é perdida em definitivo.

| Evento | Quando dispara |
| - | - |
| `verification.started` | O fluxo começou: a pessoa abriu o link e iniciou a operação. Também dispara na ramificação adaptativa de apenas selfie. |
| `verification.addfront.started` | A frente do documento foi enviada para processamento. |
| `verification.addfront.successful` | A frente foi capturada e lida com sucesso. |
| `verification.addfront.failed` | A frente falhou no antispoofing **e não restam tentativas**. |
| `verification.addback.started` | O verso do documento foi enviado para processamento. |
| `verification.addback.successful` | O verso foi capturado e lido com sucesso. |
| `verification.addback.failed` | O verso falhou no antispoofing **e não restam tentativas**. |
| `verification.register.started` | A selfie foi enviada para prova de vida e comparação facial. |
| `verification.register.successful` | A prova de vida e a comparação facial passaram. |
| `verification.register.failed` | A prova de vida ou a comparação facial falharam **e não restam tentativas**. |
| `verification.successful` | O fluxo inteiro terminou com sucesso. |
| `verification.failed` | A verificação falhou e não vai se recuperar. |

<Tip>
  Uma tentativa malsucedida com tentativas restantes **não** gera webhook: a pessoa apenas é convidada a tentar de novo e `remainingRetries` cai em um. Os eventos `*.failed` marcam a tentativa esgotada, que é também o momento em que o link é consumido.
</Tip>

## Payload

Todo payload tem o mesmo formato de duas chaves: um `type` e um objeto `data`.

```json theme={null}
{
  "type": "verification.addfront.successful",
  "data": {
    "_verificationId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "_externalUserId": "cliente-12345",
    "_metadata": { "campanha": "quickstart" }
  }
}
```

`_externalUserId` e `_metadata` são copiados do link que originou a verificação. Se o link não tinha `externalUserId`, o campo chega como string vazia; sem metadata, `_metadata` é `{}`.

### Geolocalização nos eventos terminais

`verification.successful` e `verification.failed` também trazem `data.geolocation`, quando houve captura de localização. Os eventos intermediários nunca trazem — em `verification.started` a captura ainda não aconteceu.

```json theme={null}
{
  "type": "verification.successful",
  "data": {
    "_verificationId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "_externalUserId": "cliente-12345",
    "_metadata": {},
    "geolocation": {
      "ip": "200.45.12.9",
      "country": "BR",
      "city": "São Paulo",
      "subdivisions": "São Paulo",
      "latitude": -23.5505,
      "longitude": -46.6333,
      "deviceLatitude": -23.5509,
      "deviceLongitude": -46.6339,
      "accuracy": 18,
      "accuracyRadius": 5,
      "source": "gps",
      "tier": "precise",
      "capturedAt": "2026-08-30T14:07:52.000Z"
    }
  }
}
```

Chaves ausentes são omitidas em vez de virem como `null`, então trate todos os campos como opcionais.

<Warning>
  A geolocalização do webhook é um subconjunto. `permission` e `sample` são removidos, e os campos antifraude `policy`, `ipDistanceKm` e `serverIp` **nunca** trafegam por webhook — exatamente como estão ausentes dos endpoints públicos. Se a sua lógica de risco precisa deles, consulte a verificação com um token depois de receber o evento.
</Warning>

## Validar a assinatura

A VU assina o payload com **HMAC-SHA256 sobre o corpo exato da requisição**, usando como chave o `webhookSecret` da sua conta, e envia o digest hexadecimal no cabeçalho **`X-Signature-SHA256`**.

Valide contra o corpo bruto. Se você fizer parse e serializar o JSON de novo, os bytes mudam e a comparação quebra.

```javascript theme={null}
const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.raw({ type: 'application/json' }));

const WEBHOOK_SECRET = process.env.VU_WEBHOOK_SECRET;

function verifySignature(rawBody, signature) {
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');
  const received = Buffer.from(signature, 'hex');
  const digest = Buffer.from(expected, 'hex');
  return received.length === digest.length && crypto.timingSafeEqual(received, digest);
}

app.post('/webhooks/vu', (req, res) => {
  const signature = req.headers['x-signature-sha256'];
  if (!signature || !verifySignature(req.body, signature)) {
    return res.status(401).send('Invalid signature');
  }

  // Responda primeiro, processe depois.
  res.status(200).send('OK');

  const event = JSON.parse(req.body);
  enqueue(event.type, event.data._verificationId, event.data._externalUserId);
});

app.listen(3000);
```

## Entrega e retentativas

<Warning>
  As retentativas são propositalmente curtas. A VU faz **até 3 tentativas, com 100 ms de intervalo**, e para assim que o seu endpoint responde `200`, `201`, `202` ou `204`. Qualquer outra coisa — um `500`, um timeout, um redirecionamento — consome uma tentativa. Depois da terceira, o evento é descartado e nunca reenviado.
</Warning>

Esse orçamento é pequeno demais para absorver um handler lento, então:

* **Responda imediatamente** e processe de forma assíncrona. Confirme em poucas centenas de milissegundos e mande o trabalho para uma fila.
* **Seja idempotente.** O mesmo evento pode chegar mais de uma vez, e `verification.started` é reemitido na ramificação adaptativa em alguns caminhos de retentativa.
* **Não trate o webhook como única fonte de verdade.** Reconcilie com `GET /verifications/{id}/status?linkId={linkId}` tudo o que tem impacto comercial: uma verificação cujo webhook se perdeu continua com o status correto na API.
* **Leia o resultado antes que ele expire.** Passado o `expiresAt` da verificação, `GET /verifications/{id}` responde `410`. Em [Planos](/pt/identity-link/planos) está como essa janela é calculada.

## Gerenciar endpoints

| Ação | Endpoint | Retorna |
| - | - | - |
| Listar todos os endpoints | `GET /webhook-endpoints` | Array de endpoints |
| Obter um endpoint | `GET /webhook-endpoints/{id}` | Um endpoint |
| Atualizar um endpoint | `PATCH /webhook-endpoints/{id}` | O endpoint atualizado |
| Excluir um endpoint | `DELETE /webhook-endpoints/{id}` | `{ "_id": "...", "deleted": true }` |

`PATCH` aceita `url` e `description`. Todas as rotas exigem o mesmo token Bearer do resto da API.

<Note>
  As rotas de `/webhook-endpoints` vivem na mesma URL base de `POST /links`: `https://api.identity.vu-one.com/api/v1`. Não há um host separado. Se elas respondem `404` com `given resource is not implemented`, a rota não correspondeu: verifique a URL base, o path e o método em [Erros](/pt/identity-link/erros#erros-de-rota).
</Note>

## Próximos passos

* Leia os dados extraídos da verificação no guia de [Resultados KYC](/pt/identity-link/kyc).
* Entenda como os eventos de prova de vida se mapeiam nos tipos de webhook em [Prova de vida](/pt/identity-link/prova-de-vida).
* Consulte os códigos de erro se o registro do webhook falhar na página de [Erros](/pt/identity-link/erros).


## Related topics

- [Início rápido: sua primeira verificação de identidade](/pt/quickstart.md)
- [Criar e Gerenciar Links de Verificação](/pt/identity-link/links.md)
- [Planos do Identity Link](/pt/identity-link/planos.md)
- [Documentação técnica da VU](/pt/index.md)
- [Prova de Vida e Biometria Facial no Identity Link](/pt/identity-link/prova-de-vida.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.