Eventos do widget
O widget emite exatamente dois eventos no element hospedeiro. Não há outros — não existe veridia:start, nem veridia:step, nem veridia:cancel. O cancelamento chega como um código de erro, não como um evento próprio.
Ambos são CustomEvents despachados com bubbles: true e composed: true, então você pode escutar no próprio element ou em qualquer ancestral (inclusive document).
veridia:complete
Dispara depois que as imagens foram enviadas e POST /v1/verify/submit retornou com sucesso.
const widget = document.querySelector('veridia-widget');
widget.addEventListener('veridia:complete', (e) => {
console.log(e.detail);
// { verificationId: "vf_AG07CDWRRFQV4T05ZXG2", status: "queued" }
});
e.detail tem exatamente estes campos:
| Campo | Tipo | Sempre | Descrição |
|---|---|---|---|
verificationId | string | Sim | O id vf_* desta verificação |
status | "queued" | "processing" | "completed" | Sim | Estado do pipeline no momento do envio |
verdict | "approved" | "review" | "rejected" | Não | Nunca definido pelo próprio widget |
Não existe userRef neste evento
Se você passou user-ref, ele não é devolvido aqui. Versões anteriores desta página afirmavam que sim; isso estava errado, e código escrito com base nisso associou vereditos a undefined silenciosamente.
userRef viaja no payload do webhook. Ele também não é retornado por GET /v1/verify/{id}. Se você precisa mapear uma verificação de volta ao seu próprio usuário a partir do navegador, guarde o mapeamento você mesmo no instante em que este evento dispara:
widget.addEventListener('veridia:complete', async (e) => {
await fetch('/api/kyc/started', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
// VOCÊ é dono desta associação. O widget não vai devolvê-la para você.
body: JSON.stringify({ verificationId: e.detail.verificationId, userId: currentUser.id }),
});
});
status não é um veredito
status é o eixo do pipeline: queued → processing → completed (ou failed). verdict é o eixo do resultado: approved / review / rejected.
status: "completed" significa que o pipeline rodou. Não diz nada sobre a pessoa ter passado ou não. Ativar uma conta quando status chega a completed admite todo candidato rejeitado — este é o erro mais caro que a API torna possível, e é fácil de cometer porque a palavra soa como sucesso.
Obtenha o veredito pelo webhook ou por um GET /v1/verify/{id} server-side com uma chave secreta.
veridia:error
Dispara quando o fluxo para de uma forma da qual o usuário não consegue se recuperar dentro do widget. O widget muda para sua tela de erro e para.
widget.addEventListener('veridia:error', (e) => {
const { code, message, detail } = e.detail;
console.error(`[veridia] ${code}: ${message}`, detail);
});
e.detail tem exatamente estes campos:
| Campo | Tipo | Sempre | Descrição |
|---|---|---|---|
code | string | Sim | Código legível por máquina, da lista abaixo |
message | string | Sim | Texto de diagnóstico em inglês — para seus logs, não para sua UI |
detail | object | Não | Presente apenas quando o erro veio da API |
message não é localizado e não foi escrito para usuários finais. O widget já mostra ao usuário uma mensagem localizada; use message para logging e suporte.
Códigos de erro
Estes são os valores reais de code. Faça switch nessas strings.
| Código | Causa | Emitido como evento |
|---|---|---|
camera_denied | O usuário negou a permissão de câmera (NotAllowedError) | Sim |
camera_unavailable | Nenhum dispositivo de câmera, ou getUserMedia falhou por qualquer outro motivo | Sim |
upload_failed | O upload de uma imagem falhou após todas as retentativas | Sim |
api_unreachable | Falha de rede, 5xx, ou backend_unavailable vindo da API | Sim |
invalid_api_key | Chave publicável ausente, malformada, desconhecida ou revogada | Sim |
insufficient_credits | O saldo do tenant é 0 (a API retorna 402) | Sim |
rate_limited | Limite de taxa atingido (a API retorna 429) | Sim |
user_cancelled | O usuário apertou cancelar | Sim |
internal_error | Qualquer outra coisa, incluindo invalid_body vindo da API | Sim |
blurry_image | Quadro sem nitidez suficiente | Não — veja abaixo |
no_face_in_selfie | Nenhum rosto encontrado na selfie | Não — veja abaixo |
blurry_image e no_face_in_selfie existem no tipo público VeridiaErrorCode, mas o widget nunca os despacha. São motivos de checagem de qualidade renderizados inline na tela de revisão, pedindo ao usuário uma nova captura. Não construa telemetria que espera por eles — ela ficará vazia para sempre.
Dois dos códigos alcançáveis dominam o tráfego real e são os que a maioria das integrações esquece:
camera_denied— um desfecho rotineiro, não uma anomalia. No Safari mobile a negação é persistente: remontar o widget não vai pedir de novo. Mostre suas próprias instruções para reativar a permissão nas configurações do navegador, e ofereça um caminho alternativo.user_cancelled— o usuário apertou cancelar. Isso é abandono, não falha. Não registre como erro, não gere alerta, e não mostre tela de falha. Conte: é a sua métrica de queda no funil.
Sobre upload_failed
O widget já refaz um upload que falhou até 3 vezes com backoff (400 ms, 1200 ms), e apenas para falhas transitórias — queda de rede, timeout, abort, 5xx. Um 4xx permanente falha imediatamente.
Então, quando upload_failed chega ao seu handler, as retentativas já se esgotaram. Este código significa um problema de rede real e persistente para aquele usuário. Instrumente-o — é o sinal que te avisa que uma região ou operadora inteira está falhando.
detail só está presente para erros da API
Quando a falha veio da API da Veridia, detail carrega o objeto detail da API literalmente (por exemplo retry_after em um 429). Quando a falha é do lado do cliente (camera_denied, user_cancelled), não há chave detail alguma. Verifique antes de lê-la.
Um handler completo
const widget = document.querySelector('veridia-widget');
widget.addEventListener('veridia:complete', async (e) => {
// Envio aceito. NÃO é um veredito. Entregue o id ao seu 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, não falha. Métrica, sem alerta, sem UI de erro.
analytics.track('kyc_abandoned');
showRestartPrompt();
break;
case 'camera_denied':
// Persistente no Safari do iOS — remontar não vai pedir de novo.
showCameraPermissionHelp();
break;
case 'camera_unavailable':
showMessage('We could not find a camera on this device.');
offerDesktopToMobileHandoff();
break;
case 'upload_failed':
case 'api_unreachable':
// Retentativas já esgotadas. Problema real de conectividade.
alerting.warn('veridia_network', { code, message });
showMessage('Connection problem. Please try again.');
break;
case 'rate_limited':
// detail.retry_after vem em segundos, quando a API o forneceu.
showMessage('Too many attempts. Please wait a moment.');
alerting.warn('veridia_rate_limited', { retryAfter: detail?.retry_after });
break;
case 'invalid_api_key':
case 'insufficient_credits':
// Problema SEU, não do usuário. Acione alguém.
alerting.critical('veridia_config', { code, message });
showMessage('Verification is temporarily unavailable.');
break;
default:
// internal_error e qualquer coisa adicionada em versões futuras.
alerting.error('veridia_unknown', { code, message });
showMessage('Something went wrong. Please try again.');
}
});
Mantenha o ramo default. Se uma versão futura do widget adicionar um código, este handler degrada para uma mensagem genérica em vez de silenciosamente não fazer nada.
Próximos passos
- Configuração — cada atributo e seu default real.
- Exemplos — estes handlers ligados a integrações completas.
- Webhooks — onde o veredito realmente chega.