Skip to main content

Widget installation

The Veridia widget is a single HTML custom element. Two <script> tags and a <veridia-widget> tag — that's the entire installation.

What gets installed

When you load the widget, your page gains:

  • A <veridia-widget> custom element you can drop anywhere
  • The face-api.js library (~6 MB, used for selfie quality + face detection)
  • The Veridia widget bundle (~150 KB, ESM module)
  • Two CustomEvents: veridia:complete and veridia:error

The face-api.js model files are loaded lazily on first use (about 14 MB) — they're cached aggressively after that.

Plain HTML

The simplest possible integration:

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>KYC Verification</title>

<!-- Order matters: face-api first, widget second -->
<script src="https://widget.xxuxe.online/face-api.js"></script>
<script type="module" src="https://widget.xxuxe.online/veridia-widget.min.js"></script>
</head>
<body>
<main>
<h1>Verify your identity</h1>

<veridia-widget
id="kyc"
publishable-key="qv_pub_test_YOUR_KEY"
api-base="https://api.xxuxe.online"
user-ref="customer-12345"
country="PY"
document-type="dni"
locale="es">
</veridia-widget>
</main>

<script>
document.getElementById('kyc').addEventListener('veridia:complete', (e) => {
console.log('Verification ID:', e.detail.verificationId);
// Send the ID to your backend to fetch the verdict
});
</script>
</body>
</html>

React (Create React App, Vite, etc.)

import { useEffect, useRef } from 'react';

export function VeridiaVerification({ userRef, onComplete, onError }) {
const widgetRef = useRef(null);

useEffect(() => {
const node = widgetRef.current;
if (!node) return;

const completeHandler = (e) => onComplete?.(e.detail);
const errorHandler = (e) => onError?.(e.detail);

node.addEventListener('veridia:complete', completeHandler);
node.addEventListener('veridia:error', errorHandler);

return () => {
node.removeEventListener('veridia:complete', completeHandler);
node.removeEventListener('veridia:error', errorHandler);
};
}, [onComplete, onError]);

return (
<veridia-widget
ref={widgetRef}
publishable-key={import.meta.env.VITE_VERIDIA_KEY}
api-base="https://api.xxuxe.online"
user-ref={userRef}
country="PY"
document-type="dni"
locale="es"
/>
);
}

Load the scripts once in index.html:

<script src="https://widget.xxuxe.online/face-api.js"></script>
<script type="module" src="https://widget.xxuxe.online/veridia-widget.min.js"></script>

TypeScript: add this to a *.d.ts file so JSX accepts the custom element:

declare namespace JSX {
interface IntrinsicElements {
'veridia-widget': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement> & {
'publishable-key': string;
'api-base': string;
'user-ref'?: string;
'country'?: string;
'document-type'?: string;
'submitted-full-name'?: string;
'require-doc-back'?: string;
'locale'?: string;
'accent-color'?: string;
},
HTMLElement
>;
}
}

Next.js

In Next.js, custom elements need to load after hydration. Use next/script with strategy="afterInteractive":

// app/layout.tsx (App Router)
import Script from 'next/script';

export default function RootLayout({ children }) {
return (
<html lang="es">
<body>
{children}
<Script
src="https://widget.xxuxe.online/face-api.js"
strategy="afterInteractive"
/>
<Script
src="https://widget.xxuxe.online/veridia-widget.min.js"
type="module"
strategy="afterInteractive"
/>
</body>
</html>
);
}
// app/onboarding/kyc/page.tsx
'use client';

import { useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';

export default function KycPage() {
const widgetRef = useRef(null);
const router = useRouter();

useEffect(() => {
const node = widgetRef.current;
if (!node) return;

const handler = async (e) => {
const { verificationId } = e.detail;

// Send to your backend
await fetch('/api/kyc/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ verificationId }),
});

router.push('/onboarding/success');
};

node.addEventListener('veridia:complete', handler);
return () => node.removeEventListener('veridia:complete', handler);
}, [router]);

return (
<main>
<h1>Verify your identity</h1>
<veridia-widget
ref={widgetRef}
publishable-key={process.env.NEXT_PUBLIC_VERIDIA_KEY}
api-base="https://api.xxuxe.online"
locale="es"
/>
</main>
);
}

Vue 3

Vue treats custom elements automatically. No special config needed:

<template>
<veridia-widget
ref="widget"
:publishable-key="publishableKey"
api-base="https://api.xxuxe.online"
:user-ref="userRef"
locale="es"
/>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const props = defineProps({
publishableKey: { type: String, required: true },
userRef: { type: String, required: true },
});

const emit = defineEmits(['complete', 'error']);
const widget = ref(null);

const completeHandler = (e) => emit('complete', e.detail);
const errorHandler = (e) => emit('error', e.detail);

onMounted(() => {
widget.value?.addEventListener('veridia:complete', completeHandler);
widget.value?.addEventListener('veridia:error', errorHandler);
});

onUnmounted(() => {
widget.value?.removeEventListener('veridia:complete', completeHandler);
widget.value?.removeEventListener('veridia:error', errorHandler);
});
</script>

Load the scripts in index.html:

<script src="https://widget.xxuxe.online/face-api.js"></script>
<script type="module" src="https://widget.xxuxe.online/veridia-widget.min.js"></script>

Angular

In Angular, you need to allow custom elements in your module:

// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';

@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}

Then use in templates:

<veridia-widget
#widget
[attr.publishable-key]="publishableKey"
api-base="https://api.xxuxe.online"
[attr.user-ref]="userRef"
locale="es"
(veridia:complete)="onComplete($event)"
(veridia:error)="onError($event)">
</veridia-widget>

Mobile webview

The widget works inside iOS WebView and Android WebView. Two requirements:

  1. Camera permissions: the host app must grant camera access. On iOS, set WKWebView.allowsInlineMediaPlayback = true. On Android, override onPermissionRequest to grant RESOURCE_VIDEO_CAPTURE.
  2. HTTPS: even in WebView, the widget refuses to run on insecure origins.

Troubleshooting

"This widget is misconfigured"

Most common: your domain isn't in the allowed origins list in the dashboard.

Fix: dashboard → API Keys → edit your key → add the domain (e.g., https://yourapp.com, or http://localhost:3000 for dev).

Widget loads but never shows the Start button

Usually face-api.js failed to load. Check the browser console for 404s or CSP errors.

Fix: verify the script URLs:

  • https://widget.xxuxe.online/face-api.js should return 200
  • https://widget.xxuxe.online/veridia-widget.min.js should return 200

"Camera not available"

The user denied camera permission, or the page isn't HTTPS.

Fix: ensure your page is served over HTTPS. Browsers block camera on http:// (except localhost).

Captures look blurry / quality flag triggers

Mobile devices auto-focus better than laptops. Tell users to take their time and hold steady.

If your audience uses mostly desktops, set accent-color and a clear UI cue, but accept that some users will need 2-3 retries.

React: warning about unknown attribute

React doesn't recognize custom elements by default. Add the TypeScript declaration above, or set suppressHydrationWarning on the parent.

Next.js: document is not defined during build

The widget uses browser APIs. Mark your page as a client component ('use client') and ensure scripts use strategy="afterInteractive".

CSP (Content Security Policy) errors

If your site has a strict CSP, add these directives:

script-src 'self' https://widget.xxuxe.online;
connect-src 'self' https://api.xxuxe.online https://*.r2.cloudflarestorage.com;
img-src 'self' blob: data: https://widget.xxuxe.online;
media-src 'self' blob:;
worker-src blob:;

The connect-src for R2 is needed for direct image upload from the browser.

Widget shows the wrong language

Set the locale attribute explicitly:

<veridia-widget locale="es">

Default is browser locale, fallback to English.

Verifying installation

A working installation should pass this checklist:

CheckHow to verify
Scripts loadDevTools Network tab — both 200
Custom element registereddocument.querySelector('veridia-widget') returns an element
Start button showsVisible "Comenzar" / "Start" button
Camera prompt appearsAfter clicking Start
Capture succeedsveridia:complete event fires with a verificationId

What's next