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.jslibrary (~6 MB, used for selfie quality + face detection) - The Veridia widget bundle (~150 KB, ESM module)
- Two CustomEvents:
veridia:completeandveridia: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:
- Camera permissions: the host app must grant camera access. On iOS, set
WKWebView.allowsInlineMediaPlayback = true. On Android, overrideonPermissionRequestto grantRESOURCE_VIDEO_CAPTURE. - 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.jsshould return200https://widget.xxuxe.online/veridia-widget.min.jsshould return200
"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:
| Check | How to verify |
|---|---|
| Scripts load | DevTools Network tab — both 200 |
| Custom element registered | document.querySelector('veridia-widget') returns an element |
| Start button shows | Visible "Comenzar" / "Start" button |
| Camera prompt appears | After clicking Start |
| Capture succeeds | veridia:complete event fires with a verificationId |
What's next
- Widget configuration — every attribute, event, styling option
- Quickstart — full integration walkthrough
- API Reference — server-side API for fetching verdicts