Auvral

Inicio rápido

De una llave de sandbox a un usuario de prueba verificado: crea una sesión, integra el SDK, recibe un webhook firmado.

Al final de esta página habrás creado una sesión de verificación, corrido una verificación real en sandbox, y recibido un webhook firmado que te dice que salió bien.

Necesitas un navegador con cámara, y un lugar donde tu endpoint de webhook sea alcanzable desde internet.

¿Prefieres verlo funcionar primero?

Pruébalo corre una verificación completa de sandbox en tu navegador, sin llave y sin código, para que sepas qué esperar antes de conectar nada de esto.

1. Consigue tu llave de sandbox y un id de flujo

En el dashboard de Auvral, ve a Integraciones, Llaves de API.

Tu organización tiene exactamente una llave activa por ambiente. La llave de sandbox se puede revelar desde esa página cuando la necesites: haz clic en Mostrar. Si la página te dice que la llave nunca ha sido revelada, haz clic en Rotar para emitir una.

Copia la llave de sandbox. Empieza con sk_auvral_test_.

Esta llave es un secreto del lado del servidor

Autentica como toda tu organización. Nunca debe aparecer en JavaScript del navegador, en el bundle de una aplicación móvil, en un repositorio público, ni en una URL. El flujo de verificación no la necesita y nunca la recibe.

Después ve a Flujos y copia el id del flujo que quieres correr.

El flujo tiene que estar publicado: un flujo que acabas de crear está Sin publicar y responde 409 FLOW_NOT_PUBLISHED. Publícalo desde su página de detalle o, mientras lo sigues editando, corre su borrador con una llave de sandbox enviando flowVersion: "draft".

2. Configura un endpoint de webhook

Ve a Integraciones, Webhooks y haz clic en Crear webhook:

  • URL del webhook tiene que ser https://. Para desarrollo local, un túnel como ngrok http 3000 te da una URL pública que reenvía a tu máquina.
  • Flujos: selecciona el flujo del paso 1. Un webhook solo recibe eventos de los flujos a los que está suscrito.
  • Secreto de firma: déjalo en blanco para que se genere uno.

Guárdalo, luego revela el secreto de firma y consérvalo junto a tu llave de API. Lo necesitas en el paso 5.

Usa Enviar evento de prueba para confirmar que tu endpoint es alcanzable antes de seguir. Entrega un evento webhook.test con los mismos headers y el mismo esquema de firma que uno real.

3. Crea una sesión

Desde tu backend, no desde el navegador:

curl -X POST "$AUVRAL_API_URL/v1/sessions" \
  -H "x-api-key: $AUVRAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flowId": "3f1c9a52-4d7e-4b3a-9c21-8e5f6a0b7d34",
    "referenceId": "user_8842",
    "metadata": { "orderId": "order-8841" },
    "allowedOrigins": ["https://app.example.com"]
  }'

La URL base de la API en este ambiente es https://api.dev.auvral.com

{
  "statusCode": 201,
  "data": {
    "sessionId": "9b2f4c1e-7a83-4d06-b5f1-2c9e8d3a6047",
    "url": "https://verify.dev.auvral.com/c/Jk3nQ7pXsR2vLm9Ab4Cd1e",
    "expiresAt": "2026-08-25T15:42:11.084Z"
  }
}

Fíjate en el sobre: el contenido va bajo data, y statusCode repite el estado HTTP. Todas las respuestas de Auvral tienen esta forma.

Tres cosas que saber sobre lo que volvió:

  • Guarda sessionId junto a tu usuario. Es como vas a reconocer el webhook.
  • url es para una sola persona y expira rápido. Crea la sesión cuando estés a punto de mostrarla, no en lote y por adelantado.
  • allowedOrigins es el origen de tu página, la que integrará el flujo. Opcional en el esquema, en la práctica obligatorio para cualquier integración embebida. Ver abajo.

Omitir allowedOrigins rompe los callbacks del SDK, en silencio

El flujo de verificación solo envía su resultado a una página cuyo origen esté en los allowedOrigins de la sesión. Si dejas el campo fuera, la lista queda vacía, así que nada coincide nunca: no se envía nada, y onResult, onClose y el indicador de carga nunca se disparan.

No hay nada en la red que inspeccionar y la verificación se completa con normalidad, webhook incluido: solo tu página se queda callada. El flujo embebido sí escribe un aviso [auvral] en la consola del navegador, que es el único lugar donde esta falla se nombra.

Pasa el origen exacto de la página que integra el flujo: esquema, host y puerto, sin ruta. https://app.example.com, no https://app.example.com/onboarding. Un valor que no es un origen se rechaza al crear la sesión, con un 400 INVALID_ALLOWED_ORIGINS que lo nombra. Lo que sí se queda callado es la lista bien formada y aun así equivocada: un puerto que no coincide, o http donde querías https.

Omítelo solo cuando abras la URL como un enlace normal en vez de integrarla.

Expón esto detrás de tu propia ruta, algo como POST /verification/start, devolviendo solo url al navegador. La llave de API se queda en el servidor.

4. Abre el flujo en el navegador

Carga el SDK y entrégale la URL:

verify.html
<script src="https://sdk.dev.auvral.com/v1/verification.js"></script>

<button id="verify">Verificar mi identidad</button>

<script>
document.getElementById("verify").addEventListener("click", async () => {
  // tu propio endpoint del paso 3, él tiene la llave de API, el navegador no
  const res = await fetch("/verification/start", { method: "POST" });
  const { url } = await res.json();

  new AuvralVerification().init({
    url,
    onResult: ({ status, verificationId, sessionId }) => {
      // Una señal de interfaz. Actualiza la pantalla; no cambies estado de negocio aquí.
      console.log("result", status, verificationId, sessionId);
    },
    onClose: ({ reason }) => {
      console.log("closed", reason);
    },
    onError: ({ code, message }) => {
      console.error("integration error", code, message);
    },
  });
});
</script>

Sandbox y producción

El snippet de arriba es la URL de sandbox. En producción, cárgalo desde

<script src="https://sdk.auvral.com/v1/verification.js"></script>

Ver SDK web para versionado e Integridad de subrecursos.

El SDK pide permiso de cámara. Si tu sitio envía un header Content-Security-Policy o Permissions-Policy, va a bloquear esto, de maneras que la consola del navegador explica mal. Lee Requisitos del navegador antes de depurar nada.

Ahora completa la verificación: fotografía un documento, tómate una selfie. En una computadora puede que te ofrezcan un código QR para terminar en tu celular. En cualquier caso, nada cambia para tu integración.

El postMessage es una señal de interfaz, no una prueba

onResult te dice lo que el navegador del usuario afirma que pasó. 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.

Usa onResult para pasar a tu siguiente pantalla. Usa el webhook del paso 5 para decidir cualquier cosa que importe.

5. Recibe y verifica el webhook

Tu endpoint ya recibió verification.started y verification.submitted. Cuando hay un veredicto recibes verification.succeeded o verification.failed:

{
  "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" }
      ]
    }
  }
}

data.verification.sessionId coincide con el sessionId que guardaste en el paso 3, y referenceId es el valor que enviaste, que es tu propio id de la persona.

Verifica la firma antes de confiar en nada de esto. Cada entrega lleva x-auvral-signature: v1=<hex>, un HMAC-SHA256 sobre `${timestamp}.${rawBody}` con el secreto de firma de tu webhook:

webhook.js
import express from "express";
import crypto from "crypto";

const app = express();

// cuerpo crudo, un objeto ya parseado no se puede verificar
app.post("/webhooks/auvral", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.get("x-auvral-signature") ?? "";
  const timestamp = req.get("x-auvral-timestamp") ?? "";
  const rawBody = req.body.toString("utf8");

  const expected = crypto
    .createHmac("sha256", process.env.AUVRAL_WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const received = signature.replace(/^v1=/, "");

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));

  if (!valid) return res.sendStatus(401);

  // rechaza cualquier cosa de más de cinco minutos
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(401);

  const event = JSON.parse(rawBody);

  // los reintentos reutilizan el id del evento, procesa cada uno una vez
  if (alreadyProcessed(req.get("x-auvral-event-id"))) return res.sendStatus(200);

  if (event.type === "verification.succeeded") {
    approveUser(event.data.verification.referenceId);
  }

  res.sendStatus(200); // confirma rápido: el emisor expira a los 10s
});

Verificar la firma es obligatorio. Cualquiera que descubra la URL de tu endpoint puede hacerle POST; la firma es lo único que distingue a Auvral de esa persona.

Verificar firmas tiene la misma lógica en Python, Go, PHP y Ruby, y explica por completo las reglas de cuerpo crudo, reproducción y deduplicación.

Listo

Desde aquí:

En esta página