Saltar al contenido principal

Panorama general

Esta página cubre cómo montar el endpoint y manejar la request. Para el contrato del payload ver Tipos de evento; para las garantías de entrega ver Reintentos.

Por qué webhooks en lugar de polling

TemaPolling a GET /v1/verify/{id}Webhooks
Latencia entre el veredicto y tu códigoTu intervalo de pollingUna request, apenas existe el veredicto
Llamadas a la API por verificación3–301 entrante
Clave requeridaClave secreta, solo del lado del servidorNinguna — en su lugar verificás una firma
Decisiones de revisión humanaTenés que seguir haciendo polling indefinidamenteLlegan como un segundo evento
Si tu servicio está caídoNo perdés nada, pero tenés que seguir haciendo pollingSe reintenta por ~12,6 minutos, después se puede volver a encolar

La última fila es la que más importa. Un veredicto review es final hasta que una persona lo decida, y eso puede ser horas después. Hacer polling para eso significa o hacer polling para siempre, o rendirte y dejar al usuario colgado. El webhook con la decisión del revisor llega cuando llega.

Configuración

Hay un webhook por tenant, configurado en el panel bajo Settings → Webhook. No hay lista de endpoints, no hay suscripción por evento, y no hay separación test/live para webhooks.

El formulario tiene dos campos:

CampoReglas
URLTiene que empezar con https://. Si no, se rechaza antes de guardar. También está sujeta a una protección SSRF.
SecretLo elegís vos. Mínimo 24 caracteres. Dejá el campo en blanco para conservar el secreto actual.

El secreto no se genera por vos y nunca se muestra de vuelta — el campo es de solo escritura. Generá uno con entropía real y guardalo donde tu aplicación lo lea:

openssl rand -hex 32

Una vez guardado, la próxima verificación que llegue a un veredicto hace POST a tu URL.

Cambiar el secreto tiene efecto inmediato

Guardar un secreto nuevo reemplaza al viejo al instante. No hay ventana de gracia con doble secreto. Desplegá el secreto nuevo en tu aplicación primero, y recién después guardalo en el panel — ver rotación.

La request

POST /webhooks/veridia HTTP/1.1
Host: yourapp.com
Content-Type: application/json
Veridia-Signature: t=1753142348,v1=4f8a3b9c01ee5d2f...
Veridia-Event: verification.approved
Veridia-Event-Id: evt_9f2c41ab7d8e4c05b6a3e17f2d904c8b

{"id":"evt_9f2c41ab7d8e4c05b6a3e17f2d904c8b","type":"verification.approved", ...}
HeaderUso
Veridia-Signaturet=<segundos unix>,v1=<hmac-sha256 en hex>. Verificá esto antes de confiar en nada.
Veridia-EventEl tipo de evento, espejo del type del cuerpo. Cómodo para routing y métricas; por sí solo no está autenticado — lo que cubre el HMAC es el cuerpo.
Veridia-Event-IdEl mismo valor que el id del cuerpo. Te permite consultar tu store de deduplicación antes de parsear el cuerpo.

La firma cubre los bytes crudos del cuerpo. No re-serialices el JSON antes de calcular el HMAC — el orden de las claves y los espacios son parte de lo que se firmó. Ver Verificación de firma.

Respondé 2xx dentro de 10 segundos

El timeout de entrega es de 10 segundos (5 segundos para conectar). Cualquier cosa más lenta cuenta como intento fallido y se reintenta, lo que significa que a tu handler lento-pero-exitoso le van a pedir hacer el mismo trabajo otra vez.

Confirmá primero, trabajá después:

app.post('/webhooks/veridia', express.raw({ type: 'application/json' }), (req, res) => {
// 1. Verificar la firma — rápido, y lo único que debe pasar en línea.
if (!verifyVeridiaSignature(req.header('Veridia-Signature'), req.body, SECRET)) {
return res.status(401).send('invalid signature');
}

// 2. Confirmar. Todo lo que sigue a partir de acá corre por tu cuenta.
res.status(200).send('ok');

// 3. Hacer el trabajo. Los fallos acá son tuyos para reintentar — la entrega
// ya está confirmada y no se va a volver a enviar.
const payload = JSON.parse(req.body.toString('utf8'));
enqueue(payload).catch(err => logger.error({ err, eventId: payload.id }));
});

Ese último comentario es el trade-off que estás aceptando. Confirmar temprano implica que un crash entre el paso 2 y el paso 3 pierde el evento, así que persistí el payload de forma durable (una fila, un mensaje en una cola) como la confirmación, y hacé el procesamiento real a partir de ahí. Los ejemplos siguen todos ese patrón.

Códigos de estado sobre los que actuamos

Tu respuestaQué pasa
2xxEntregado. Listo.
5xx, timeout, error de conexiónSe reintenta según el esquema de backoff
408, 429Se reintenta
Cualquier otro 4xxFallo permanente. No se reintenta — un 401 o un 404 significa que reintentar la request idéntica no puede funcionar.

Un fallo permanente igual queda registrado en el panel y se puede volver a encolar a mano una vez que arreglaste la causa.

status no es verdict

Esto aplica a la API de polling más que a los webhooks, pero es el error más caro que la API de este producto habilita, y los handlers de webhooks lo heredan cada vez que cruzan datos contra GET /v1/verify/{id}:

  • status es el estado del pipeline: queued, processing, completed, failed.
  • verdict es el resultado: approved, review, rejected.

status === "completed" significa que el pipeline corrió, no que la persona pasó. Ramificar sobre eso para dar acceso deja entrar a todos los solicitantes rechazados. Los payloads de webhook no tienen campo status en absoluto — traen type y verdict, y los dos son resultados. Ramificá sobre esos.

Pruebas locales

Los webhooks no pueden llegar a localhost. Esto no es una configuración que se pueda relajar: la URL tiene que ser https://, y la protección SSRF rechaza direcciones de loopback y privadas al momento de enviar. Un túnel es la única forma de recibir entregas reales en desarrollo.

HerramientaNotas
ngrokPlan gratuito; la opción de siempre
localtunnelGratis, open source
Cloudflare TunnelGratis; mejor para un entorno de desarrollo de larga vida
ngrok http 3000
# Forwarding https://abc123.ngrok.io -> http://localhost:3000

# Poné https://abc123.ngrok.io/webhooks/veridia en Settings → Webhook

Para ejercitar el handler sin correr una verificación, reproducí un cuerpo con una firma fresca — ver el script de replay. Firmá exactamente los bytes que mandás.

Checklist

  • Verificá Veridia-Signature antes de confiar en el cuerpo. Siempre.
  • Rechazá si |now - t| > 300 segundos.
  • Respondé 2xx dentro de 10 segundos; persistí primero, procesá después.
  • Deduplicá por id (o por el header Veridia-Event-Id). La entrega es al menos una vez.
  • Hacé el switch sobre type. No sobre event — no existe el campo event.
  • Leé scores con claves snake_case, y chequeá null en scores.liveness.
  • Tratá fieldsExtracted como datos personales, también en tus logs.
  • Logueá id y verificationId en cada entrega — es lo que te va a pedir soporte.

Qué sigue