Web SDK
Web Component <provvi-camera> para captura com assinatura ICP-Brasil server-side, embutível
diretamente em páginas web.
Tier de garantia. O Web SDK é um cliente de captura para deterrência de fraude (fraud deterrence): a foto é assinada server-side com ICP-Brasil A1 (PROVVI_INTERNAL); o SDK não fornece proveniência de hardware device-side (GPS/sensores = telemetria, não prova de origem). Para cadeia de prova legal com âncora de hardware, use o Android SDK ou o iOS SDK.
Modelo de credenciais (leia primeiro)
Seção intitulada “Modelo de credenciais (leia primeiro)”O Web SDK trabalha com três credenciais distintas — confundi-las é o erro de integração mais comum e mais perigoso.
| Credencial | Formato | Onde fica | Para quê |
|---|---|---|---|
| API Key | pvv_api_* | Backend do integrador — NUNCA no frontend | Criar sessões de captura |
| License Key | pvv_live_* / pvv_sand_* / pvv_stag_* | Frontend (no SDK) — por design | Validar o licenciamento do SDK |
| Token de sessão | UUID | Transitório (backend → frontend) | Identificar uma captura específica |
🔒 A API Key cria sessões — e quem cria uma sessão pode obter imagens assinadas com ICP-Brasil sob a SUA conta. Por isso ela é backend-only. Se você embutir a API Key no navegador, qualquer pessoa a lê (view-source / aba de rede) e pode forjar capturas autenticadas no seu nome e consumir sua cota. A License Key, ao contrário, é desenhada para o frontend (o backend a controla por domínio/bundle). Nunca coloque a API Key no client.
Fluxo correto
Seção intitulada “Fluxo correto”1. [Seu backend] POST /web/sessions (com x-api-key) ──→ recebe { token }2. [Seu backend] entrega o token ao frontend3. [Frontend] camera.open({ token }) ──→ SDK captura, assina e devolve o resultadoUm exemplo de backend mínimo (Node, serverless-agnóstico) que guarda a API Key e expõe um endpoint
fino de criação de sessão é fornecido pela Provvi (create-session.mjs).
Requisitos
Seção intitulada “Requisitos”| Item | Valor |
|---|---|
| Browsers | Chrome 80+, Safari 14+, Firefox 78+ |
| APIs | getUserMedia (câmera) + SubtleCrypto.digest (hash) |
| Protocolo | HTTPS obrigatório (a câmera não abre em origem insegura) |
Instalação
Seção intitulada “Instalação”O SDK é distribuído como ES module único via CDN da Provvi (sdk.provvi.com.br), com
Subresource Integrity (SRI). A equipe Provvi fornece a URL versionada e o hash de integridade.
<script type="importmap">{ "imports": { "@provvi/web-sdk": "https://sdk.provvi.com.br/provvi-camera.<hash>.es.js" }}</script><script type="module"> import { useProvviCamera, EVENTS } from '@provvi/web-sdk';</script>O arquivo é imutável (nome versionado por hash de conteúdo) e servido com
integritySRI. Você também pode auto-hospedar o bundle, se preferir.
1. Inicialização
Seção intitulada “1. Inicialização”const camera = useProvviCamera({ licenseKey: 'pvv_live_SuaChaveAqui', // obrigatório (frontend, por design) appVersion: 'meuapp-1.0', // opcional, informativo});useProvviCamera() cria um singleton do Web Component <provvi-camera> e o insere no document.body.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
licenseKey | string | Sim | Chave de licença (pvv_live_* / pvv_sand_* / pvv_stag_*). |
appVersion | string | Não | Versão do app integrador (informativo). |
overlay | object | Não | Máscara-guia de enquadramento (ver Máscara-guia). |
2. Criar a sessão (no seu backend)
Seção intitulada “2. Criar a sessão (no seu backend)”A sessão é criada no seu backend com a API Key, que retorna um token (use o exemplo
create-session.mjs fornecido pela Provvi como ponto de partida). O frontend chama apenas o
seu endpoint:
// Frontend — chama o SEU backend (sem API Key)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();3. Abrir a captura
Seção intitulada “3. Abrir a captura”camera.open({ token, // obrigatório — vem do seu backend customData: { ref: 'VIN-123' }, // opcional — ecoado de volta nos eventos});| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
token | string | Sim | Token de sessão criado pelo seu backend. |
customData | any | Não | Dados arbitrários, ecoados de volta nos eventos. |
photoOrder | number | Não | Ordem da foto (default: da sessão). |
gpsTimeoutMs | number | Não | Timeout de aquisição de GPS, em ms. |
overlay | object | Não | Máscara-guia (sobrescreve a de useProvviCamera). |
Fluxo interno
Seção intitulada “Fluxo interno”Ao chamar open(), o SDK executa automaticamente: valida a licença → busca a sessão → solicita
permissões (câmera/sensores) → abre a câmera com a guia de enquadramento → adquire GPS e bússola em
background → aguarda o toque no botão de captura → captura e processa o frame → faz upload para
assinatura ICP-Brasil. Para sessões multi-foto, repete automaticamente.
4. Eventos
Seção intitulada “4. Eventos”O SDK emite CustomEvent padrão do DOM (todos com bubbles: true e composed: true).
camera.addEventListener(EVENTS.UPLOADED, (e) => { const { result } = e.detail.capture; console.log('Sessão:', result.session_id); console.log('JPEG assinado:', result.authenticated_jpeg_url);});
camera.addEventListener(EVENTS.ERROR, (e) => { console.error(`Erro ${e.detail.error.code}: ${e.detail.error.message}`);});| Evento | Constante | Quando | e.detail |
|---|---|---|---|
opened | EVENTS.OPENED | Câmera e sensores ativos | {} |
capture | EVENTS.CAPTURE | Frame capturado, antes do upload | { capture } |
uploading | EVENTS.UPLOADING | Upload iniciado | { capture } |
uploaded | EVENTS.UPLOADED | Assinatura concluída | { capture } — capture.result (abaixo) |
error | EVENTS.ERROR | Erro em qualquer etapa | { capture, error } (error é ProvviError) |
closed | EVENTS.CLOSED | Câmera encerrada | {} |
Estrutura do capture
Seção intitulada “Estrutura do capture”{ id: 'uuid-v4', // ID da captura status: 'UPLOADED', // NEW | READY | UPLOADING | UPLOADED | ERROR capturedAt: '2026-06-26T12:05:30Z', // ISO 8601 result: { // presente apenas no evento UPLOADED session_id: 'sess_abc123', authenticated_jpeg_url: 'https://…', // JPEG com manifesto + ICP-Brasil EMBUTIDOS (presigned) complete: true, // true se todas as fotos da sessão foram concluídas next_photo: null, // { order, label } quando complete=false }}A prova de autenticidade viaja dentro do JPEG (
authenticated_jpeg_url) — manifesto C2PA + assinatura ICP-Brasil embutidos, autoverificáveis em verify.provvi.com.br. Não há URL de manifesto separada. GPS, integridade e demais sinais já estão no manifesto.
Obtendo a imagem
Seção intitulada “Obtendo a imagem”camera.addEventListener(EVENTS.UPLOADED, (e) => { const { result } = e.detail.capture; document.getElementById('foto').src = result.authenticated_jpeg_url; // Para armazenamento permanente, baixe e guarde no SEU backend antes da URL expirar.});As URLs presigned expiram. Se precisar de acesso permanente, baixe a imagem e armazene no seu backend.
5. Fechar e verificar suporte
Seção intitulada “5. Fechar e verificar suporte”camera.close(); // para o stream, esconde a UI, emite CLOSEDcamera.remove(); // close() + remove do DOM
if (!camera.isUserAgentSupported()) { alert('Seu navegador não suporta captura de câmera.');}Máscara-guia (overlay)
Seção intitulada “Máscara-guia (overlay)”A guia auxilia o enquadramento (forma + escurecimento externo + texto). Configurável em
useProvviCamera({ overlay }) ou por captura em open({ overlay }).
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 rosto na moldura', },});| Campo | Tipo | Default | Descrição |
|---|---|---|---|
shape | string | square | rectangle | oval | square | none |
inset | object | preset por shape | { top, bottom, left, right } em %/vmin (retângulo/oval) |
width / height | string | 72vmin (square) | bounding box centralizado (alternativa ao inset) |
color | string | accent | cor da borda |
lineWidth | number | 2.5 | espessura da borda (px) |
dimOutside | number | 0.45 | escurecimento fora da guia (0..1) |
label | string | guia do perfil | texto de instrução |
A máscara é guia VISUAL apenas — nunca recorta a imagem. A imagem capturada e assinada é sempre o frame inteiro do sensor. Use
shape: 'none'para não desenhar guia alguma.
Customização visual (CSS)
Seção intitulada “Customização visual (CSS)”O componente usa Shadow DOM; ajuste via CSS custom properties:
provvi-camera { --provvi-accent: #f59e0b; --provvi-bg: #0f172a; --provvi-surface: #1e293b; --provvi-success: #22c55e; --provvi-danger: #ef4444;}Os erros chegam via o evento ERROR (e.detail.error) — instâncias de ProvviError com code,
message, details e toJSON(). Trate por instanceof ou por error.code.
| Classe | Código(s) | Quando ocorre |
|---|---|---|
ProvviError | 1000 | Erro genérico (base) |
ProvviUserAgentError | 2001 | Browser não suporta getUserMedia |
ProvviStreamError | 3001 / 3002 | Falha ao abrir/usar a câmera |
ProvviAPIError | 4001 / 4002 | Falha na API (sessão indisponível ou ingest) |
ProvviCaptureError | 5001 / 5002 | Falha na orquestração (ex.: token ausente) |
ProvviGeolocationError | 6001–6003 | Falha no GPS |
ProvviUploadError | 7001 | Falha no upload |
ProvviLicenseError | 8001–8003 | Licença ausente / inválida / erro de rede |
A constante CODES mapeia os nomes aos códigos numéricos. Consulte Erros para a tabela
completa.
API pública do módulo
Seção intitulada “API pública do módulo”| Export | Tipo | Descrição |
|---|---|---|
useProvviCamera(config) | function | Cria o singleton <provvi-camera> |
EVENTS | object | Nomes dos eventos (OPENED, CAPTURE, UPLOADING, UPLOADED, ERROR, CLOSED) |
STATUSES | object | Status da captura (NEW, READY, UPLOADING, UPLOADED, ERROR) |
VERSION | string | Versão do SDK |
ProvviError + subclasses, CODES | class/object | Erros tipados (ver acima) |
Métodos do elemento: open(options), close(), remove(), isUserAgentSupported().
Exemplo completo
Seção intitulada “Exemplo completo”<!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.<hash>.es.js" } } </script> <script type="module"> import { useProvviCamera, EVENTS } from '@provvi/web-sdk';
const camera = useProvviCamera({ licenseKey: 'pvv_live_SuaChaveAqui' });
camera.addEventListener(EVENTS.UPLOADED, (e) => { const { result } = e.detail.capture; 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. 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-123' }), }); const { token } = await resp.json();
// 2. O SDK abre a câmera com o token (sem API Key no frontend) camera.open({ token }); }); </script></body></html>