Webhooks
Cómo Auvral le cuenta a tu backend lo que pasó: entrega, reintentos, idempotencia y por qué esta es la única señal autoritativa.
Los webhooks son la forma en que tu backend se entera del resultado de una verificación. Hoy son la única forma, así que trata a tu manejador de webhooks como el lugar donde la verificación cambia el estado de tu sistema.
Configura uno
Dashboard, Integraciones, Webhooks, Crear webhook.
| Campo | Notas |
|---|---|
| Nombre | Para tu propia referencia. Hasta 100 caracteres. |
| URL del webhook | Tiene que ser https://. |
| Flujos | Para qué flujos recibe eventos este webhook. Al menos uno. |
| Secreto de firma | Déjalo en blanco para generar uno, o pon el tuyo. |
Un webhook está suscrito a flujos, no a tipos de evento: recibe los cuatro tipos de
evento de las verificaciones de ese flujo. Filtra por type en tu manejador.
Revela el secreto de firma desde la página del webhook y guárdalo junto a tu llave de API. Es una credencial aparte, con su propia rotación.
Enviar evento de prueba entrega un evento webhook.test con los mismos headers y el
mismo esquema de firma que uno real. Úsalo para confirmar que tu endpoint es alcanzable y
para desarrollar contra él.
Los cuatro eventos
type | Cuándo |
|---|---|
verification.started | Tu usuario empezó la verificación |
verification.submitted | Tu usuario terminó de enviar; todavía no hay veredicto |
verification.succeeded | Veredicto: verificado |
verification.failed | Veredicto: rechazado |
Más webhook.test, enviado solo por el botón de prueba del dashboard.
El evento de éxito es verification.succeeded
No verification.completed. Si estás trabajando con una guía de integración vieja o con
un ejemplo que menciona verification.completed o un cuerpo plano { verificationId, sessionId, event }, está desactualizado. Ver
Referencia de eventos.
verification.failed es una verificación rechazada, no un fallo de entrega. Es un
resultado normal.
Cuerpo del evento
Todos los eventos tienen el mismo sobre:
{
"id": "6f1d0a4c-8e22-4b7e-9a13-5c0be9f4d871",
"type": "verification.succeeded",
"createdAt": "2026-08-25T15:12:44.301Z",
"data": {
"verification": {
"id": "b47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "VERIFIED",
"flowId": "3f1c9a52-4d7e-4b3a-9c21-8e5f6a0b7d34",
"flowName": "Onboarding",
"sessionId": "9b2f4c1e-7a83-4d06-b5f1-2c9e8d3a6047",
"referenceId": "user_8842",
"metadata": { "orderId": "order-8841" },
"startedAt": "2026-08-25T15:11:02.884Z",
"endedAt": "2026-08-25T15:12:44.120Z",
"steps": [
{ "name": "document_verification", "status": "VERIFIED" },
{ "name": "biometric_verification", "status": "VERIFIED" }
]
}
}
}Ubica a tu usuario con data.verification.sessionId (el sessionId que guardaste cuando
creaste la sesión) o con data.verification.referenceId (tu propio id de la persona).
El cuerpo no lleva ningún dato personal: ni nombre, ni número de documento, ni imagen.
El detalle campo por campo está en la Referencia de eventos.
Headers
| Header | Valor |
|---|---|
x-auvral-signature | v1=<hex>, HMAC-SHA256 de `${timestamp}.${rawBody}` |
x-auvral-timestamp | Segundos Unix; el mismo valor que entró en la firma |
x-auvral-event-id | El id del evento. Estable entre reintentos, deduplica con este |
x-auvral-event-type | por ejemplo verification.succeeded |
x-auvral-delivery-id | Este intento de entrega en particular |
x-auvral-attempt | Número de intento |
user-agent | Auvral-Webhooks/2 |
Verifica la firma. Esto no es opcional.
La URL de tu webhook es alcanzable por cualquiera que la descubra, y la firma es lo único que separa un evento real de Auvral de uno falsificado que dice éxito.
Tres reglas, y las tres le han mordido a integraciones reales:
- Lee el cuerpo crudo antes de parsearlo. La firma cubre bytes exactos. Cualquier framework que te entregue un objeto ya parseado destruyó lo que necesitas: un objeto JSON re-serializado no va a coincidir.
- Rechaza marcas de tiempo viejas. Sin una comprobación de frescura, una petición capturada se puede reproducir contra ti para siempre. Cinco minutos es una ventana razonable.
- Compara en tiempo constante.
crypto.timingSafeEqualen Node,hmac.compare_digesten Python.
Verificar firmas tiene código que funciona en Node, Python, Go, PHP y Ruby.
Entrega, reintentos e idempotencia
- Éxito es cualquier
2xx. Cualquier otra cosa, un4xx, un5xx, un timeout o un fallo de DNS, es un intento fallido. - El timeout es de 10 segundos por intento.
- Hasta 6 intentos, con esperas de 1 minuto, 5 minutos, 30 minutos, 2 horas y luego 6 horas entre ellos. Un endpoint que está caído toda una tarde igual recibe sus eventos.
- El cuerpo de tu respuesta se guarda y se ve en el dashboard, así que un mensaje de error útil te facilita tu propia depuración.
Haz que tu manejador sea idempotente
Los reintentos reutilizan el mismo x-auvral-event-id, así que un manejador que no sea
idempotente puede procesar la misma verificación hasta seis veces: seis correos de
aprobación, seis cuentas acreditadas. Registra el id del evento, e ignora uno que ya hayas
procesado.
Confirma rápido. Devuelve 2xx apenas hayas verificado la firma y guardado el evento de
forma durable; haz el trabajo de verdad después, fuera de línea. Un manejador que hace
trabajo lento en línea va a chocar con el timeout de 10 segundos y va a recibir un
reintento, que es como empieza el problema de los duplicados.
Los eventos no están ordenados: un verification.submitted reintentado puede llegar
después de verification.succeeded. Guía tu lógica por type y
data.verification.status, no por el orden de llegada.
Monitoreo
La página del webhook en el dashboard muestra cada intento de entrega (marca de tiempo, estado HTTP, cuerpo de la respuesta, número de intento) y te deja reintentar uno fallido a mano o pausar el webhook sin borrarlo.
El navegador nunca decide
El postMessage es una señal de interfaz, no una prueba
Nunca desbloquees una cuenta, apruebes un pago o cambies el estado de tu negocio a partir
de él. La verdad es el webhook o un GET del lado del servidor. Vive en el navegador, y
el usuario controla su navegador.
El callback onResult del SDK llega en el navegador de tu usuario. Es
genuinamente útil para actualizar tu interfaz de inmediato, y genuinamente falsificable
por la persona sentada frente a él.
La división que hay que sostener:
| Señal | Confía en ella para | Nunca confíes en ella para |
|---|---|---|
onResult en el navegador | Mostrar una pantalla de éxito, avanzar tu interfaz | Estado de la cuenta, pagos, límites, permisos |
| Un webhook con firma verificada | Todo | Nada |
Un patrón limpio: onResult consulta a tu propio backend, que reporta éxito solo una
vez que tu manejador de webhooks ya corrió.
Reconciliar, no sondear
Un webhook perdido o procesado dos veces sí se puede reconciliar preguntándole a la API
después: GET /v1/sessions/{sessionId} y
GET /v1/verifications/{id} responden con tu llave de API.
Eso no vuelve consejos las dos cosas de abajo. Verifica las firmas y haz tu manejador idempotente: la lectura es una herramienta de reparación para la entrega que perdiste, no un reemplazo de la entrega. Úsala de forma programada o cuando falte algo, nunca en un bucle mientras corre una verificación, y nunca como aquello que espera la página de tu usuario.