Pular para o conteúdo

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.


O Web SDK trabalha com três credenciais distintas — confundi-las é o erro de integração mais comum e mais perigoso.

CredencialFormatoOnde ficaPara quê
API Keypvv_api_*Backend do integrador — NUNCA no frontendCriar sessões de captura
License Keypvv_live_* / pvv_sand_* / pvv_stag_*Frontend (no SDK) — por designValidar o licenciamento do SDK
Token de sessãoUUIDTransitó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.

1. [Seu backend] POST /web/sessions (com x-api-key) ──→ recebe { token }
2. [Seu backend] entrega o token ao frontend
3. [Frontend] camera.open({ token }) ──→ SDK captura, assina e devolve o resultado

Um 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).


ItemValor
BrowsersChrome 80+, Safari 14+, Firefox 78+
APIsgetUserMedia (câmera) + SubtleCrypto.digest (hash)
ProtocoloHTTPS obrigatório (a câmera não abre em origem insegura)

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 integrity SRI. Você também pode auto-hospedar o bundle, se preferir.


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âmetroTipoObrigatórioDescrição
licenseKeystringSimChave de licença (pvv_live_* / pvv_sand_* / pvv_stag_*).
appVersionstringNãoVersão do app integrador (informativo).
overlayobjectNãoMáscara-guia de enquadramento (ver Máscara-guia).

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();

camera.open({
token, // obrigatório — vem do seu backend
customData: { ref: 'VIN-123' }, // opcional — ecoado de volta nos eventos
});
ParâmetroTipoObrigatórioDescrição
tokenstringSimToken de sessão criado pelo seu backend.
customDataanyNãoDados arbitrários, ecoados de volta nos eventos.
photoOrdernumberNãoOrdem da foto (default: da sessão).
gpsTimeoutMsnumberNãoTimeout de aquisição de GPS, em ms.
overlayobjectNãoMáscara-guia (sobrescreve a de useProvviCamera).

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.


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}`);
});
EventoConstanteQuandoe.detail
openedEVENTS.OPENEDCâmera e sensores ativos{}
captureEVENTS.CAPTUREFrame capturado, antes do upload{ capture }
uploadingEVENTS.UPLOADINGUpload iniciado{ capture }
uploadedEVENTS.UPLOADEDAssinatura concluída{ capture }capture.result (abaixo)
errorEVENTS.ERRORErro em qualquer etapa{ capture, error } (error é ProvviError)
closedEVENTS.CLOSEDCâmera encerrada{}
{
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.

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.


camera.close(); // para o stream, esconde a UI, emite CLOSED
camera.remove(); // close() + remove do DOM
if (!camera.isUserAgentSupported()) {
alert('Seu navegador não suporta captura de câmera.');
}

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',
},
});
CampoTipoDefaultDescrição
shapestringsquarerectangle | oval | square | none
insetobjectpreset por shape{ top, bottom, left, right } em %/vmin (retângulo/oval)
width / heightstring72vmin (square)bounding box centralizado (alternativa ao inset)
colorstringaccentcor da borda
lineWidthnumber2.5espessura da borda (px)
dimOutsidenumber0.45escurecimento fora da guia (0..1)
labelstringguia do perfiltexto 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.

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.

ClasseCódigo(s)Quando ocorre
ProvviError1000Erro genérico (base)
ProvviUserAgentError2001Browser não suporta getUserMedia
ProvviStreamError3001 / 3002Falha ao abrir/usar a câmera
ProvviAPIError4001 / 4002Falha na API (sessão indisponível ou ingest)
ProvviCaptureError5001 / 5002Falha na orquestração (ex.: token ausente)
ProvviGeolocationError6001–6003Falha no GPS
ProvviUploadError7001Falha no upload
ProvviLicenseError8001–8003Licença ausente / inválida / erro de rede

A constante CODES mapeia os nomes aos códigos numéricos. Consulte Erros para a tabela completa.


ExportTipoDescrição
useProvviCamera(config)functionCria o singleton <provvi-camera>
EVENTSobjectNomes dos eventos (OPENED, CAPTURE, UPLOADING, UPLOADED, ERROR, CLOSED)
STATUSESobjectStatus da captura (NEW, READY, UPLOADING, UPLOADED, ERROR)
VERSIONstringVersão do SDK
ProvviError + subclasses, CODESclass/objectErros tipados (ver acima)

Métodos do elemento: open(options), close(), remove(), isUserAgentSupported().


<!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>