Saltar al contenido principal

Eventos del widget

El widget emite exactamente dos eventos sobre el elemento anfitrión. No hay otros: no existe veridia:start, ni veridia:step, ni veridia:cancel. La cancelación llega como un código de error, no como un evento propio.

Ambos son CustomEvent despachados con bubbles: true y composed: true, así que podés escuchar en el elemento mismo o en cualquier ancestro (incluido document).

veridia:complete

Se dispara después de que las imágenes fueron subidas y POST /v1/verify/submit respondió con éxito.

const widget = document.querySelector('veridia-widget');

widget.addEventListener('veridia:complete', (e) => {
console.log(e.detail);
// { verificationId: "vf_AG07CDWRRFQV4T05ZXG2", status: "queued" }
});

e.detail tiene exactamente estos campos:

CampoTipoSiempreDescripción
verificationIdstringEl id vf_* de esta verificación
status"queued" | "processing" | "completed"Estado del pipeline al momento del envío
verdict"approved" | "review" | "rejected"NoEl widget nunca lo define

En este evento no hay userRef

Si pasaste user-ref, no se devuelve acá. Versiones anteriores de esta página afirmaban que sí; era falso, y el código escrito contra eso asociaba veredictos con undefined en silencio.

userRef viaja en el payload del webhook. Tampoco lo devuelve GET /v1/verify/{id}. Si necesitás mapear una verificación de vuelta a tu propio usuario desde el navegador, guardá vos mismo el mapeo en el momento en que se dispara este evento:

widget.addEventListener('veridia:complete', async (e) => {
await fetch('/api/kyc/started', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
// ESTA asociación es tuya. El widget no te la va a devolver.
body: JSON.stringify({ verificationId: e.detail.verificationId, userId: currentUser.id }),
});
});

status no es un veredicto

status es el eje del pipeline: queuedprocessingcompleted (o failed). verdict es el eje del resultado: approved / review / rejected.

status: "completed" significa que el pipeline corrió. No dice nada sobre si la persona aprobó. Activar una cuenta cuando status llega a completed admite a todos los solicitantes rechazados: es el error más caro que la API deja disponible, y es fácil de cometer porque la palabra suena a éxito.

Obtené el veredicto desde el webhook o desde un GET /v1/verify/{id} del lado del servidor con una clave secreta.

veridia:error

Se dispara cuando el flujo se detiene de una forma de la que el usuario no puede recuperarse dentro del widget. El widget cambia a su pantalla de error y se detiene.

widget.addEventListener('veridia:error', (e) => {
const { code, message, detail } = e.detail;
console.error(`[veridia] ${code}: ${message}`, detail);
});

e.detail tiene exactamente estos campos:

CampoTipoSiempreDescripción
codestringCódigo legible por máquina, de la lista de abajo
messagestringTexto diagnóstico en inglés — para tus logs, no para tu interfaz
detailobjectNoPresente solo cuando el error vino de la API

message no está traducido y no está escrito para usuarios finales. El widget ya le muestra al usuario un mensaje localizado; usá message para logging y soporte.

Códigos de error

Estos son los valores reales de code. Ramificá sobre estas cadenas.

CódigoCausaSe emite como evento
camera_deniedEl usuario denegó el permiso de cámara (NotAllowedError)
camera_unavailableNo hay dispositivo de cámara, o getUserMedia falló por cualquier otro motivo
upload_failedFalló la subida de una imagen después de todos los reintentos
api_unreachableFalla de red, 5xx, o backend_unavailable de la API
invalid_api_keyClave publicable faltante, malformada, desconocida o revocada
insufficient_creditsEl saldo del tenant es 0 (la API devuelve 402)
rate_limitedSe alcanzó el límite de tasa (la API devuelve 429)
user_cancelledEl usuario presionó Cancelar
internal_errorCualquier otra cosa, incluido invalid_body de la API
blurry_imageCuadro demasiado desenfocadoNo — ver abajo
no_face_in_selfieNo se encontró rostro en la selfieNo — ver abajo

blurry_image y no_face_in_selfie existen en el tipo público VeridiaErrorCode, pero el widget nunca los despacha. Son motivos de control de calidad que se muestran inline en la pantalla de revisión, pidiendo al usuario que repita la toma. No construyas telemetría que los espere: va a quedar vacía para siempre.

Dos de los códigos alcanzables dominan el tráfico real y son los que más integraciones olvidan:

  • camera_denied — un desenlace de rutina, no una anomalía. En Safari móvil la denegación queda pegada: volver a montar el widget no vuelve a pedir permiso. Mostrá tus propias instrucciones para reactivar el permiso en la configuración del navegador, y ofrecé un camino alternativo.
  • user_cancelled — el usuario presionó Cancelar. Esto es abandono, no una falla. No lo registres como error, no dispares alertas y no muestres una pantalla de fracaso. Contalo: es tu métrica de abandono del embudo.

Sobre upload_failed

El widget ya reintenta una subida fallida hasta 3 veces con backoff (400 ms, 1200 ms), y solo ante fallas transitorias: caída de red, timeout, abort, 5xx. Un 4xx permanente falla de inmediato.

Así que cuando upload_failed llega a tu handler, los reintentos ya se agotaron. Este código significa un problema de red real y persistente para ese usuario. Instrumentalo: es la señal que te avisa que una región o una operadora entera está fallando.

detail solo está presente en errores de la API

Cuando la falla vino de la API de Veridia, detail lleva el objeto detail de la API tal cual (por ejemplo retry_after en un 429). Cuando la falla es del lado del cliente (camera_denied, user_cancelled), no existe la clave detail en absoluto. Verificá antes de leerla.

Un handler completo

const widget = document.querySelector('veridia-widget');

widget.addEventListener('veridia:complete', async (e) => {
// Envío aceptado. NO es un veredicto. Pasale el id a tu backend.
await fetch('/api/kyc/submitted', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ verificationId: e.detail.verificationId }),
});
showPendingScreen();
});

widget.addEventListener('veridia:error', (e) => {
const { code, message, detail } = e.detail;

switch (code) {
case 'user_cancelled':
// Abandono, no una falla. Métrica, sin alerta, sin interfaz de error.
analytics.track('kyc_abandoned');
showRestartPrompt();
break;

case 'camera_denied':
// Queda pegado en iOS Safari: volver a montar no vuelve a pedir permiso.
showCameraPermissionHelp();
break;

case 'camera_unavailable':
showMessage('No pudimos encontrar una cámara en este dispositivo.');
offerDesktopToMobileHandoff();
break;

case 'upload_failed':
case 'api_unreachable':
// Reintentos ya agotados. Problema real de conectividad.
alerting.warn('veridia_network', { code, message });
showMessage('Problema de conexión. Intentá de nuevo.');
break;

case 'rate_limited':
// detail.retry_after viene en segundos, cuando la API lo proveyó.
showMessage('Demasiados intentos. Esperá un momento.');
alerting.warn('veridia_rate_limited', { retryAfter: detail?.retry_after });
break;

case 'invalid_api_key':
case 'insufficient_credits':
// Problema TUYO, no del usuario. Avisale a alguien.
alerting.critical('veridia_config', { code, message });
showMessage('La verificación no está disponible temporalmente.');
break;

default:
// internal_error y cualquier cosa que se agregue en versiones futuras.
alerting.error('veridia_unknown', { code, message });
showMessage('Algo salió mal. Intentá de nuevo.');
}
});

Dejá la rama default. Si una versión futura del widget agrega un código, este handler degrada a un mensaje genérico en lugar de no hacer nada en silencio.

Qué sigue

  • Configuración — cada atributo y su valor por defecto real.
  • Ejemplos — estos handlers conectados en integraciones completas.
  • Webhooks — donde el veredicto llega realmente.