Instalação do widget
O widget da Veridia é um único custom element HTML. Duas tags <script> e uma tag <veridia-widget> — isso é a instalação inteira.
O que é instalado
Quando você carrega o widget, sua página ganha:
- Um custom element
<veridia-widget>que você pode colocar em qualquer lugar - A biblioteca
face-api.js(~1,33 MB, usada para qualidade da selfie + detecção de rosto) - O bundle do widget da Veridia (~45 KB minificado)
- Dois CustomEvents:
veridia:completeeveridia:error
Um modelo de detecção de rosto (tiny_face_detector, ~196 KB) é carregado sob demanda na primeira captura e fica em cache depois disso. O custo total com cache frio é de cerca de 1,6 MB.
HTML puro
A integração mais simples possível:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>KYC Verification</title>
<!-- A ordem importa: face-api primeiro, widget depois -->
<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_pubt_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);
// Envie o ID para o seu backend buscar o veredito
});
</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"
/>
);
}
Carregue os scripts uma única vez no 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: adicione isto a um arquivo *.d.ts para que o JSX aceite o 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;
'active-liveness'?: string;
},
HTMLElement
>;
}
}
Next.js
No Next.js, custom elements precisam carregar depois da hidratação. Use next/script com 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;
// Envie para o seu 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
O Vue trata custom elements automaticamente. Nenhuma configuração especial é necessária:
<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>
Carregue os scripts no 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
No Angular, você precisa permitir custom elements no seu módulo:
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}
Depois use nos 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>
WebView mobile
O widget funciona dentro de WebView do iOS e do Android. Dois requisitos:
- Permissões de câmera: o app hospedeiro precisa conceder acesso à câmera. No iOS, defina
WKWebView.allowsInlineMediaPlayback = true. No Android, sobrescrevaonPermissionRequestpara concederRESOURCE_VIDEO_CAPTURE. - HTTPS: mesmo em WebView, o widget se recusa a rodar em origens inseguras.
Troubleshooting
O widget mostra uma tela de erro genérica
A tela de erro do widget sempre mostra o mesmo texto genérico, seja qual for a falha. Para descobrir o que realmente aconteceu, escute veridia:error e leia e.detail.code:
document.querySelector('veridia-widget')
.addEventListener('veridia:error', (e) => console.error(e.detail.code, e.detail.message));
As causas prováveis, na ordem em que você deve checá-las:
code | O que verificar |
|---|---|
invalid_api_key | A chave está ausente no element, digitada errado, revogada, ou pertence ao outro ambiente (qv_pubt_ é teste, qv_pub_ é live) |
insufficient_credits | O saldo do seu tenant é 0. Comum em uma conta de teste que acabou — e fácil de confundir com um problema de configuração |
camera_denied | O usuário negou a câmera. No Safari do iOS isso é persistente: remontar o widget não vai pedir de novo |
api_unreachable | api-base errado, falha de rede, ou CSP bloqueando connect-src |
Não é uma causa provável: origens permitidas. Toda chave é criada com a lista de origens vazia, e uma lista vazia permite todas as origens — então, por padrão, nada está sendo bloqueado por esse motivo. O dashboard também não expõe esse campo atualmente. Veja Autenticação.
Note também que origens são comparadas contra o hostname puro (yourapp.com, localhost), nunca contra uma URL completa. https://yourapp.com e http://localhost:3000 não vão dar match com nada.
O widget carrega mas nunca mostra o botão de iniciar
Normalmente o face-api.js falhou ao carregar. Verifique 404s ou erros de CSP no console do navegador.
Correção: confira as URLs dos scripts:
https://widget.xxuxe.online/face-api.jsdeve retornar200https://widget.xxuxe.online/veridia-widget.min.jsdeve retornar200
"Câmera não disponível"
O usuário negou a permissão de câmera, ou a página não está em HTTPS.
Correção: garanta que sua página seja servida por HTTPS. Navegadores bloqueiam a câmera em http:// (exceto localhost).
As capturas saem borradas / o botão de confirmar continua desabilitado
Dispositivos móveis fazem foco automático melhor que notebooks. Diga aos usuários para segurar firme e ter paciência.
Isso nunca emite um evento — o borrão é tratado inline, na tela de revisão, com um pedido de nova captura. E não trava indefinidamente: após dois quadros rejeitados consecutivos o widget aceita a foto mesmo assim, porque uma checagem de qualidade que se engana sobre um documento perfeitamente legível prenderia o usuário para sempre. Um documento genuinamente inutilizável ainda falha nos próprios controles do backend.
Nas etapas de documento o usuário também tem a opção de enviar a foto da galeria — muitas vezes o caminho mais rápido para superar uma webcam de desktop teimosa. Veja Customização.
React: aviso sobre atributo desconhecido
O React não reconhece custom elements por padrão. Adicione a declaração de TypeScript acima, ou defina suppressHydrationWarning no elemento pai.
Next.js: document is not defined durante o build
O widget usa APIs do navegador. Marque sua página como client component ('use client') e garanta que os scripts usem strategy="afterInteractive".
Erros de CSP (Content Security Policy)
Se seu site tem uma CSP estrita, adicione estas diretivas:
script-src 'self' https://widget.xxuxe.online;
connect-src 'self' https://api.xxuxe.online https://widget.xxuxe.online;
img-src 'self' blob: data: https://widget.xxuxe.online;
media-src 'self' blob:;
style-src 'self' 'unsafe-inline';
worker-src blob:;
Três dessas linhas são fáceis de errar, e dois dos erros falham silenciosamente:
connect-srcprecisa deapi.xxuxe.onlineporque as imagens são enviadas para o Worker da Veridia, não para uma URL R2 pré-assinada. Colocar*.r2.cloudflarestorage.comna allow-list não faz nada — nenhuma requisição é feita para lá.connect-srctambém precisa dewidget.xxuxe.online. O modelo de detecção de rosto é buscado de onde quer que o bundle do widget tenha sido servido. Se isso faltar, o fetch do modelo é bloqueado, o widget captura a falha com umconsole.warne continua funcionando sem detecção de rosto — nenhum erro visível, mas você perde o controle de "nenhum rosto na selfie" e o score de prova de vida enviado ao/submit. Uma degradação silenciosa de um sinal antifraude, causada por uma CSP que parece correta.style-srcprecisa permitir estilos inline. O widget injeta um element<style>dentro do seu shadow root a cada render. Sob umdefault-src 'self'estrito semstyle-src, o widget renderiza completamente sem estilo — sem cores de botão, sem guia de enquadramento, layout quebrado — e nada aparece na aba de rede para explicar isso.
O widget mostra o idioma errado
Defina o atributo locale explicitamente:
<veridia-widget locale="es">
O default é o locale do navegador, com fallback para inglês.
Verificando a instalação
Uma instalação funcionando deve passar neste checklist:
| Checagem | Como verificar |
|---|---|
| Scripts carregam | Aba Network do DevTools — ambos 200 |
| Custom element registrado | document.querySelector('veridia-widget') retorna um element |
| Botão de iniciar aparece | Botão "Comenzar" / "Start" visível |
| Prompt de câmera aparece | Depois de clicar em iniciar |
| Captura tem sucesso | O evento veridia:complete dispara com um verificationId |
Próximos passos
- Configuração do widget — cada atributo e seu default real
- Eventos — os dois eventos e a tabela completa de códigos de erro
- Exemplos — integrações completas, incluindo a entrega ao backend
- Referência da API — API server-side para buscar vereditos