> ## 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 de Identity Link: eventos del ciclo de verificación

> Recibe notificaciones en tiempo real en tu servidor para cada paso del proceso de verificación de identidad con Identity Link.

Los webhooks permiten que tu backend reciba notificaciones HTTP en tiempo real mientras una persona avanza por una verificación de Identity Link. En lugar de consultar la API en bucle, registrás una URL y VU publica un payload JSON firmado cada vez que un paso se completa o falla.

<Note>
  Los webhooks son parte de la API, así que requieren plan **Premium o Pro**, igual que `POST /oauth/token`. Ver [Planes](/identity-link/planes).
</Note>

## Registrar un 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://tu-app.com/webhooks/vu",
    "description": "Eventos de verificación en producción"
  }'
```

El cuerpo acepta exactamente dos campos: `url` (obligatorio) y `description` (opcional). Cualquier otro campo se rechaza. La llamada responde `201`:

```json theme={null}
{
  "_id": "b6b0a1f2-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
  "userId": "9f8e7d6c-5b4a-4938-2716-0f5e4d3c2b1a",
  "description": "Eventos de verificación en producción",
  "url": "https://tu-app.com/webhooks/vu",
  "createdAt": "2026-08-30T14:02:11.000Z",
  "updatedAt": "2026-08-30T14:02:11.000Z"
}
```

<Warning>
  **La respuesta no trae ningún secreto.** La firma no es por endpoint: hay un único secreto de firma por cuenta, y todos los endpoints que registres reciben payloads firmados con él.
</Warning>

## Dónde vive el secreto de firma

Tu `webhookSecret` es un valor de 256 bits que la aplicación muestra en **Configuración → Desarrolladores**, junto a tu Client Id y tu Client Secret. El mismo valor vuelve en un `GET /users/me` autenticado.

Para rotarlo, usá **Refrescar** en esa pantalla o llamá:

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

La respuesta contiene el secreto nuevo. La rotación tiene efecto inmediato, así que desplegá el valor nuevo en tu validador antes de rotar, o asumí una ventana corta de entregas rechazadas.

## Eventos

Doce eventos cubren el ciclo de vida. `started` se dispara cuando un paso empieza, `successful` cuando se aprueba y `failed` cuando se pierde de forma definitiva.

| Evento | Cuándo se dispara |
| - | - |
| `verification.started` | Empezó el flujo: la persona abrió el enlace e inició la operación. También se dispara en la rama adaptativa de solo selfie. |
| `verification.addfront.started` | Se envió el frente del documento para procesar. |
| `verification.addfront.successful` | El frente se capturó y se leyó correctamente. |
| `verification.addfront.failed` | El frente falló el antispoofing **y no quedan reintentos**. |
| `verification.addback.started` | Se envió el dorso del documento para procesar. |
| `verification.addback.successful` | El dorso se capturó y se leyó correctamente. |
| `verification.addback.failed` | El dorso falló el antispoofing **y no quedan reintentos**. |
| `verification.register.started` | Se envió la selfie para prueba de vida y comparación facial. |
| `verification.register.successful` | La prueba de vida y la comparación facial dieron bien. |
| `verification.register.failed` | La prueba de vida o la comparación facial fallaron **y no quedan reintentos**. |
| `verification.successful` | El flujo completo terminó con éxito. |
| `verification.failed` | La verificación falló y no se va a recuperar. |

<Tip>
  Un intento fallido con reintentos disponibles **no** genera webhook: simplemente se le pide a la persona que vuelva a intentar y `remainingRetries` baja en uno. Los eventos `*.failed` marcan el intento agotado, que es también el momento en que el enlace se consume.
</Tip>

## Payload

Todos los payloads tienen la misma forma de dos claves: un `type` y un objeto `data`.

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

`_externalUserId` y `_metadata` se copian del enlace que originó la verificación. Si el enlace no llevaba `externalUserId`, el campo llega como cadena vacía; sin metadata, `_metadata` es `{}`.

### Geolocalización en los eventos terminales

`verification.successful` y `verification.failed` además traen `data.geolocation`, cuando se capturó una ubicación. Los eventos intermedios nunca la traen: en `verification.started` la captura todavía no ocurrió.

```json theme={null}
{
  "type": "verification.successful",
  "data": {
    "_verificationId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "_externalUserId": "cliente-12345",
    "_metadata": {},
    "geolocation": {
      "ip": "200.45.12.9",
      "country": "AR",
      "city": "Buenos Aires",
      "subdivisions": "Ciudad Autónoma de Buenos Aires",
      "latitude": -34.6037,
      "longitude": -58.3816,
      "deviceLatitude": -34.6041,
      "deviceLongitude": -58.3822,
      "accuracy": 18,
      "accuracyRadius": 5,
      "source": "gps",
      "tier": "precise",
      "capturedAt": "2026-08-30T14:07:52.000Z"
    }
  }
}
```

Las claves ausentes se omiten en lugar de viajar como `null`, así que tratá todos los campos como opcionales.

<Warning>
  La geolocalización del webhook es un subconjunto. `permission` y `sample` se quitan, y los campos antifraude `policy`, `ipDistanceKm` y `serverIp` **nunca** viajan por webhook, igual que están ausentes de los endpoints públicos. Si tu lógica de riesgo los necesita, consultá la verificación con un token después de recibir el evento.
</Warning>

## Validar la firma

VU firma el payload con **HMAC-SHA256 sobre el cuerpo exacto de la request**, usando como clave el `webhookSecret` de tu cuenta, y manda el digest hexadecimal en el header **`X-Signature-SHA256`**.

Validá contra el cuerpo crudo. Si parseás y volvés a serializar el JSON, los bytes cambian y la comparación falla.

```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');
  }

  // Primero respondé, después trabajá.
  res.status(200).send('OK');

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

app.listen(3000);
```

## Entrega y reintentos

<Warning>
  Los reintentos son deliberadamente cortos. VU hace **hasta 3 intentos, separados por 100 ms**, y se detiene apenas tu endpoint responde `200`, `201`, `202` o `204`. Cualquier otra cosa —un `500`, un timeout, un redirect— consume un intento. Después del tercero, el evento se descarta y no se reenvía nunca.
</Warning>

Ese presupuesto es demasiado chico para absorber un handler lento, así que:

* **Respondé de inmediato** y procesá de forma asincrónica. Confirmá dentro de unos pocos cientos de milisegundos y mandá el trabajo a una cola.
* **Sé idempotente.** El mismo evento puede llegar más de una vez, y `verification.started` se vuelve a emitir en la rama adaptativa en algunos caminos de reintento.
* **No trates al webhook como única fuente de verdad.** Reconciliá con `GET /verifications/{id}/status?linkId={linkId}` todo lo que tenga impacto comercial: una verificación cuyo webhook se perdió sigue teniendo su estado correcto en la API.
* **Leé el resultado antes de que expire.** Pasado el `expiresAt` de la verificación, `GET /verifications/{id}` responde `410`. En [Planes](/identity-link/planes) está cómo se calcula esa ventana.

## Administrar endpoints

| Acción | Endpoint | Devuelve |
| - | - | - |
| Listar todos los endpoints | `GET /webhook-endpoints` | Array de endpoints |
| Obtener un endpoint | `GET /webhook-endpoints/{id}` | Un endpoint |
| Actualizar un endpoint | `PATCH /webhook-endpoints/{id}` | El endpoint actualizado |
| Eliminar un endpoint | `DELETE /webhook-endpoints/{id}` | `{ "_id": "...", "deleted": true }` |

`PATCH` acepta `url` y `description`. Todas las rutas requieren el mismo token Bearer que el resto de la API.

<Note>
  Las rutas de `/webhook-endpoints` viven en la misma URL base que `POST /links`: `https://api.identity.vu-one.com/api/v1`. No hay un host aparte. Si responden `404` con `given resource is not implemented`, la ruta no coincidió: revisá la URL base, el path y el método en [Errores](/identity-link/errores#errores-404-recurso-no-encontrado).
</Note>

## Siguientes pasos

* Leé los datos extraídos de la verificación en la guía de [Resultados KYC](/identity-link/kyc).
* Entendé cómo se corresponden los eventos de prueba de vida con los tipos de webhook en [Prueba de vida](/identity-link/prueba-de-vida).
* Revisá los códigos de error si falla el registro del webhook en la página de [Errores](/identity-link/errores).


## Related topics

- [Inicio rápido: tu primera verificación de identidad](/quickstart.md)
- [Changelog de Identity Link API](/identity-link/changelog.md)
- [Identity Link API: guía de integración completa](/identity-link/introduccion.md)
- [Prueba de vida y biometría facial en Identity Link](/identity-link/prueba-de-vida.md)
- [Leer resultados KYC de verificaciones de identidad](/identity-link/kyc.md)


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