Pular para o conteúdo

Passo 4 · Erros e bloqueios

Toda falha de captura chega ao seu código de duas formas: como um erro no evento error do componente <provvi-camera> (Tabela A) ou, por trás dele, como um erro HTTP de um dos endpoints da API (Tabela B). Esta página é orientada a ação: para cada caso, o que fazer no seu app ou o que orientar o usuário.

Alguns bloqueios (imagem estática, recaptura, divergência de localização) são gates de dissuasão de fraude (fraud deterrence). Lembre que a garantia do fluxo web é assinatura server-side ICP-Brasil A1 sobre os bytes recebidos — não há âncora de hardware device-side. Esses gates elevam o custo do ataque; não substituem a cadeia de prova com hardware do SDK mobile.

Trate primeiro por classe (instanceof) para o caso amplo, depois por código (error.code) para o granular. O objeto do erro traz error.code, error.details e error.toJSON() (sem stack trace, seguro para telemetria).

import {
CODES,
ProvviBrowserError,
ProvviLicenseError,
ProvviStreamError,
} from '@provvi/web-sdk';
camera.addEventListener('error', (e) => {
const { error } = e.detail; // ProvviError com .code e .details
// 1) Por classe — trata o caso amplo
if (error instanceof ProvviBrowserError) {
mostrarQrParaCelular(); // 2002 — abra no celular
return;
}
if (error instanceof ProvviLicenseError) {
// error.details.reason: INVALID_KEY | REVOKED | EXPIRED |
// VOLUME_EXCEEDED | CLIENT_SUSPENDED | ORIGIN_NOT_ALLOWED
mostrarErroLicenca(error.details?.reason);
return;
}
if (error instanceof ProvviStreamError) {
pedirPermissaoCamera(); // 3001 — permita a câmera
return;
}
// 2) Por código — trata o granular
switch (error.code) {
case CODES.TOKEN_REQUIRED: // 5002 — bug de integração
console.error('open() foi chamado sem token');
break;
case CODES.SESSION_FETCH_FAILED: // 4001 — sessão inválida
recriarSessao();
break;
case CODES.INGEST_FAILED: // 4002 — mapeie pelo HTTP
tratarIngest(error.details?.status);
break;
default:
reportarTelemetria(error.toJSON?.() ?? error);
}
});

O código INGEST_FAILED (4002) embrulha os erros HTTP da captura: o status real fica em error.details.status. Mapeie-o para a ação do usuário (Tabela B, seção POST /web/ingest):

function tratarIngest(status) {
switch (status) {
case 400: // imagem estática — peça uma captura ao vivo
mostrar('Aponte para o objeto real e capture ao vivo (não use uma foto pronta).');
break;
case 403: // license, relógio ou nonce — inspecione o corpo
mostrar('Verifique a data e a hora do dispositivo e recarregue a página.');
break;
case 404:
case 410: // sessão inválida ou expirada — crie outra
recriarSessao();
break;
case 409: // não foi possível processar — refaça a captura
mostrar('Não foi possível processar esta captura. Tente novamente.');
break;
case 422: // recaptura ou localização — objeto físico + permitir GPS
mostrar('Fotografe o objeto físico e permita o acesso à localização.');
break;
case 502: // transitório — tente de novo
mostrar('Falha temporária. Tente novamente em instantes.');
break;
default:
mostrar('Não foi possível concluir a captura.');
}
}

Estes são os erros com throw-site real hoje. Cada um chega em e.detail.error.

code Nome Quando ocorre O que fazer
2002 BROWSER_NOT_SUPPORTED Navegador fora da allowlist. Só Safari mobile ou Chrome mobile passam; desktop, Firefox e Chrome/Edge/Opera no iOS são bloqueados. Use camera.isUserAgentSupported() antes de abrir e conduza o usuário no seu fluxo quando não for suportado (ex.: orientá-lo a abrir no celular).
3001 STREAM_OPEN_FAILED Falha ao abrir a câmera — permissão negada ou câmera em uso por outro app. A câmera é obrigatória. Peça ao usuário para permitir a câmera e fechar apps que a estejam usando; ofereça repetir.
5002 TOKEN_REQUIRED open() foi chamado sem token. Bug de integração — passe o token da sessão criada no backend (Passo 1).
8001 LICENSE_KEY_MISSING licenseKey não foi informada em useProvviCamera() nem em open(). Bug de integração — informe a license key pvv_* do frontend.
8002 LICENSE_INVALID Licença rejeitada na validação. O motivo está em error.details.reason. Depende do reason: EXPIRED/VOLUME_EXCEEDED → renovar/ampliar; REVOKED/CLIENT_SUSPENDED → contatar a Provvi; ORIGIN_NOT_ALLOWED → domínio não autorizado (ver Passo 6).
8003 LICENSE_NETWORK_ERROR Falha de rede ao validar a licença. Verifique a conexão do usuário e ofereça repetir.
4001 SESSION_FETCH_FAILED Não foi possível carregar a sessão (não encontrada, expirada, já usada ou muitas recargas). Crie uma nova sessão no backend e reabra o componente. Detalhe HTTP na Tabela B, seção GET /web/sessions/:token.
4002 INGEST_FAILED Falha ao enviar a captura (inclui falha de upload). O status HTTP está em error.details.status. Mapeie details.status pela Tabela B, seção POST /web/ingest.
reservados (não ocorrem hoje) 1000, 3002, 5001, 6001, 6002, 6003, 7001 existem em CODES mas não têm throw-site atual. Não trate como comportamento; cubra-os apenas no default de telemetria.

Os corpos abaixo são os literais retornados pela API. O componente chama GET /web/sessions e POST /web/ingest internamente e os expõe via os códigos 4001/4002 (acima). A criação de sessão você mesmo faz no backend (Passo 1).

POST /web/sessions — criação da sessão (backend)

Seção intitulada “POST /web/sessions — criação da sessão (backend)”
HTTP Corpo O que fazer
401 { "error": "API key inválida ou ausente" } (variações: "...ou revogada", "...apenas pvv_api_* aceito") Confira o header x-api-key: pvv_api_... do seu backend. Nunca envie a x-api-key ao frontend.
403 { "error": "License inválida para esta API key" } A license_key do corpo não pertence a esta x-api-key. Use a license emitida para a mesma conta.
429 { "error": "Limite de criação de sessões atingido..." } Reduza a taxa de criação de sessões e aplique backoff. Cada sessão é única — não recrie desnecessariamente.
503 transitório (contador indisponível, fail-closed) Repita a criação com backoff exponencial. Não trate como falha permanente.

GET /web/sessions/:token — chamado pelo componente (chega como 4001)

Seção intitulada “GET /web/sessions/:token — chamado pelo componente (chega como 4001)”
HTTP Corpo O que fazer
404 { "error": "Sessão não encontrada" } Token inválido ou nunca emitido. Crie uma nova sessão no backend.
410 { "error": "Sessão expirada ou já utilizada" } Sessões são single-use e têm validade limitada. Gere uma sessão nova.
429 { "error": "Limite de tentativas atingido para esta sessão" } A página foi recarregada vezes demais. Oriente o usuário a recomeçar com uma sessão nova.

POST /web/ingest — chamado pelo componente (chega como 4002, details.status)

Seção intitulada “POST /web/ingest — chamado pelo componente (chega como 4002, details.status)”
HTTP Corpo O que fazer
400 { "error": "Imagem estática detectada..." } Foi enviada uma imagem sem sinal de captura ao vivo. Oriente o usuário a apontar para o objeto real e capturar pela câmera.
403 { "error": "..." } — licença revalidada (revogada/suspensa/expirada) A licença deixou de ser válida durante a sessão. Trate como licença inválida (ver Tabela A, 8002).
403 { "error": "Relógio do dispositivo fora de sincronia..." } Oriente o usuário a verificar a data e a hora do dispositivo e capturar de novo.
403 { "error": "..." } — nonce ausente/inválido Oriente o usuário a recarregar a página para renovar a sessão.
404 { "error": "Sessão não encontrada" } Sessão inexistente. Crie uma nova.
409 { "error": "Esta captura não pôde ser processada." } Corpo opaco por design. Oriente o usuário a repetir a captura; se persistir, use uma sessão nova.
410 { "error": "Sessão expirada ou já utilizada" } Sessão fora de validade ou já usada. Gere uma nova.
422 { "error": "Imagem rejeitada — possível recaptura", "recapture_risk": ... } Foto de tela/foto-de-foto detectada. Oriente o usuário a fotografar o objeto físico, sem telas no enquadramento.
422 { "error": "...", "block_reason": "..." } — divergência ou ausência de localização Só ocorre com verificação de GPS em modo enforce. Oriente o usuário a permitir o acesso à localização. Ajuste o modo em Passo 6 · Configuração.
502 { "error": "Erro de assinatura: ..." } Falha transitória na assinatura. Ofereça repetir a captura em instantes.

Os contratos HTTP completos (payloads, campos e envelopes) estão na Referência da API Web; a API do componente e os códigos, na Referência do Web SDK.


← Ciclo de captura · Próximo → Resultados