Passo 2 · Frontend: o componente de captura
No Passo 1 o seu backend criou uma sessão e recebeu um token. Agora você embute
o componente de captura no frontend e o abre com esse token. O componente é um Web Component
(<provvi-camera>) que faz toda a orquestração — câmera, sensores, upload e assinatura — sem que você
precise construir nada disso.
Tier de garantia. A captura web é assinada server-side com ICP-Brasil A1 sobre os bytes recebidos — deterrência de fraude (fraud deterrence). Ela não carrega âncora de proveniência de hardware device-side. Para cadeia de prova com âncora de hardware, use os SDKs mobile. Isto vale para qualquer configuração desta página.
Instalação
Seção intitulada “Instalação”O SDK é distribuído como ES module único pela CDN da Provvi (sdk.provvi.com.br), com
Subresource Integrity (SRI). Você declara um importmap apontando para a URL versionada e o hash
de integridade correspondente:
<script type="importmap">{ "imports": { "@provvi/web-sdk": "https://sdk.provvi.com.br/provvi-camera.24ac452a5f8c.es.js" }, "integrity": { "https://sdk.provvi.com.br/provvi-camera.24ac452a5f8c.es.js": "sha384-L6aTUccXz27jJ2/DxXzRmY9yWgcYvYClTN36DJjU0tgg/WsVJ6uXaY4DLCcKeAGo" }}</script><script type="module"> import { useProvviCamera, EVENTS } from '@provvi/web-sdk';</script>Versionamento por hash. O nome do arquivo carrega o hash do conteúdo — o bundle é imutável. Quando o SDK é atualizado, a Provvi publica uma nova URL + novo hash; troque os dois juntos. Se preferir, você também pode auto-hospedar o bundle, mantendo o mesmo
integrity.
Instanciar o componente
Seção intitulada “Instanciar o componente”useProvviCamera() cria o elemento <provvi-camera>, insere no document.body e devolve a instância:
const camera = useProvviCamera({ licenseKey: 'pvv_live_SuaChaveAqui', // obrigatório — pode ficar no frontend, por design appVersion: 'meuapp-1.0', // opcional — informativo overlay: { shape: 'rectangle' }, // opcional — máscara-guia (ver abaixo)});| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
licenseKey |
string | Sim | Sua chave de licença (pvv_live_* / pvv_sand_* / pvv_stag_*). Ela é desenhada para viver no frontend: a admissão é feita por domínio/origin (só funciona nos domínios que você registrou). |
appVersion |
string | Não | Identificador da versão do seu app (informativo, vai nos metadados). |
overlay |
object | Não | Máscara-guia de enquadramento — ver Máscara-guia. |
A referência completa dos parâmetros e exports do módulo está em Referência → Web SDK.
⚠️ O componente é um singleton por página. Chamar
useProvviCamera()de novo retorna a mesma instância e ignora a nova configuração — ooverlay/appVersionda segunda chamada não têm efeito. Eremove()tira o elemento do DOM mas não reseta o singleton: uma chamada posterior auseProvviCamera()devolve a instância antiga, não uma nova. Configure uma vez, no boot da página. Se precisar mudar a configuração por captura (ex.:overlay), passe noopen()— ver Passo 3.
O token vem do SEU backend — nunca da Provvi direto
Seção intitulada “O token vem do SEU backend — nunca da Provvi direto”O frontend nunca fala com a API da Provvi para criar sessão. Ele chama o seu endpoint (o do
Passo 1), recebe o token e passa para o componente:
// Frontend — chama o SEU backend (a API Key nunca desce ao navegador)const resp = await fetch('/api/create-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reference_id: 'VIN-9BWZZZ377VT004251' }),});const { token } = await resp.json();
camera.open({ token }); // abre a câmera com o token da sessãoA partir daí o componente conversa com a Provvi por conta própria (busca a sessão, faz o upload assinado). Você só reage aos eventos que ele emite — coberto no Passo 3 · Ciclo de captura.
A regra de ouro do Passo 1 vale aqui: a
x-api-key(pvv_api_*) é backend-only. O frontend recebe apenas otoken. Se você embutir a API Key no navegador, qualquer pessoa a lê e forja capturas na sua conta.
Máscara-guia (overlay)
Seção intitulada “Máscara-guia (overlay)”O overlay desenha uma moldura de enquadramento sobre o preview (forma + escurecimento externo +
texto de instrução). Configure em useProvviCamera({ overlay }) para toda a sessão, ou em
open({ overlay }) para uma captura específica.
useProvviCamera({ licenseKey: 'pvv_live_...', overlay: { shape: 'oval', // 'rectangle' | 'oval' | 'square' | 'none' color: '#f59e0b', lineWidth: 2.5, dimOutside: 0.45, // 0..1 — escurecimento fora da guia label: 'Centralize o veículo na moldura', },});| Campo | Tipo | Descrição |
|---|---|---|
shape |
string | rectangle | oval | square | none |
inset |
object | { top, bottom, left, right } da moldura (retângulo/oval) |
width / height |
string | bounding box centralizado — alternativa ao inset |
color |
string | cor da borda |
lineWidth |
number | espessura da borda (px) |
dimOutside |
number | escurecimento fora da guia (0..1) |
label |
string | texto de instrução exibido ao usuário |
A máscara é guia VISUAL apenas — nunca recorta a imagem. A imagem capturada e assinada é sempre o frame inteiro do sensor, independentemente da forma da guia. Use
shape: 'none'para não desenhar moldura alguma.
Requisitos do ambiente
Seção intitulada “Requisitos do ambiente”HTTPS obrigatório
Seção intitulada “HTTPS obrigatório”A câmera não abre em origem insegura. Sirva a página por HTTPS (ou http://localhost em
desenvolvimento). Sem isso, o navegador bloqueia getUserMedia antes de o componente rodar.
CSP e CORS (connect-src)
Seção intitulada “CSP e CORS (connect-src)”O componente faz requisições diretas do navegador para três destinos. Se a sua página usa
Content-Security-Policy, o connect-src precisa liberar todos eles:
Content-Security-Policy: connect-src 'self' https://api.provvi.com.br https://sessions.provvi.com.br https://*.s3.sa-east-1.amazonaws.com ;| Destino | Para quê |
|---|---|
https://api.provvi.com.br |
Validação da license key |
https://sessions.provvi.com.br |
API de sessões/captura da Provvi |
https://*.s3.sa-east-1.amazonaws.com |
Upload do frame para a URL presigned |
Se você também define img-src, libere o mesmo host de S3 para exibir o JPEG autenticado retornado por
URL presigned.
Requisito: captura só em navegador móvel
Seção intitulada “Requisito: captura só em navegador móvel”A captura roda apenas em navegadores móveis (Safari ou Chrome no celular) — é onde getUserMedia e
os sensores estão disponíveis. Em desktop (e em navegadores fora da allowlist), o componente bloqueia
com um erro de navegador não suportado.
Você embute o Web SDK no seu web app; o usuário final captura no próprio celular, dentro do seu
fluxo. Detecte o ambiente com camera.isUserAgentSupported() antes de open() e, se não for
suportado, trate no seu fluxo (por exemplo, oriente o usuário a abrir a página no celular) — o fallback
é decisão sua:
const camera = useProvviCamera({ licenseKey: 'pvv_live_SuaChaveAqui' });
if (!camera.isUserAgentSupported()) { // Navegador não suportado (desktop, etc.): trate no seu fluxo — // ex.: instrua o usuário a abrir a página no celular.} else { document.getElementById('btn').addEventListener('click', iniciarCaptura);}A matriz de navegadores suportados está em Como funciona e a lista de erros de navegador em Passo 4 · Erros.
Exemplo HTML completo (mínimo funcional)
Seção intitulada “Exemplo HTML completo (mínimo funcional)”<!DOCTYPE html><html lang="pt-BR"><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /></head><body> <button id="btn">Iniciar captura</button> <img id="foto" alt="" />
<script type="importmap"> { "imports": { "@provvi/web-sdk": "https://sdk.provvi.com.br/provvi-camera.24ac452a5f8c.es.js" }, "integrity": { "https://sdk.provvi.com.br/provvi-camera.24ac452a5f8c.es.js": "sha384-L6aTUccXz27jJ2/DxXzRmY9yWgcYvYClTN36DJjU0tgg/WsVJ6uXaY4DLCcKeAGo" } } </script>
<script type="module"> import { useProvviCamera, EVENTS } from '@provvi/web-sdk';
// Configure uma vez (singleton por página) const camera = useProvviCamera({ licenseKey: 'pvv_live_SuaChaveAqui', overlay: { shape: 'rectangle', label: 'Enquadre o veículo' }, });
// Navegador não suportado (desktop, etc.) → trate no seu fluxo (ex.: oriente a abrir no celular) if (!camera.isUserAgentSupported()) { document.getElementById('btn').disabled = true; }
camera.addEventListener(EVENTS.UPLOADED, (e) => { const { result } = e.detail.capture; if (result?.authenticated_jpeg_url) { document.getElementById('foto').src = result.authenticated_jpeg_url; } });
camera.addEventListener(EVENTS.ERROR, (e) => { alert(`Erro ${e.detail.error.code}: ${e.detail.error.message}`); });
document.getElementById('btn').addEventListener('click', async () => { // 1. O SEU backend cria a sessão (com a API Key, server-side) e devolve o token const resp = await fetch('/api/create-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reference_id: 'VIN-9BWZZZ377VT004251' }), }); const { token } = await resp.json();
// 2. O componente abre a câmera com o token (sem API Key no frontend) camera.open({ token }); }); </script></body></html>Esse exemplo instancia o componente, cria a sessão no seu backend e abre a captura. O detalhamento dos eventos, das permissões, do comportamento multi-foto e do redirect automático está no próximo passo.