Passo 1 · Backend — criar a sessão
Toda captura web começa por uma sessão, criada do seu backend. Nesta página você monta esse endpoint, entende o payload completo e sai com um { token, expires_at } para entregar ao frontend.
Por que a sessão nasce no backend
Seção intitulada “Por que a sessão nasce no backend”A criação de sessão é autenticada pela x-api-key (pvv_api_*) — uma credencial de alto privilégio: quem a possui pode mintar sessões e, com cada token, obter imagens assinadas com ICP-Brasil sob a sua conta.
Por isso ela nunca desce ao frontend. Se a x-api-key ficasse no browser, qualquer visitante a leria no view-source ou na aba de rede. O fluxo correto é:
Frontend ──POST /api/create-session (sem api key)──▶ SEU backendSEU backend ──POST /web/sessions (com x-api-key)────▶ ProvviSEU backend ◀──── { token, expires_at } ───────────── ProvviFrontend ◀──── { token, expires_at } ──────────────── SEU backendO frontend recebe apenas { token, expires_at }. Essa é a regra de ouro: a x-api-key mora só no servidor.
POST /web/sessions
Seção intitulada “POST /web/sessions”URL base (API Gateway):
https://sessions.provvi.com.brHeaders:
| Header | Valor |
|---|---|
Content-Type |
application/json |
x-api-key |
Sua API Key (pvv_api_*) |
Body:
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
reference_id |
string | Sim | ID do recurso no seu sistema (anúncio, veículo, apólice) que você associa a esta captura. |
license_key |
string | Sim | Sua license key (pvv_*) — a mesma usada no frontend. Enviada do backend; amarra a licença à sessão (ver abaixo). Ausente → 400. |
O armazenamento do JPEG autenticado não é um parâmetro do create — é derivado da sua licença (store_asset_policy). Ver Configuração.
Resposta 200:
{ "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "expires_at": "2026-07-07T12:30:00Z"}São só esses dois campos. O token tem validade de 30 minutos e é single-use.
License amarrada à sessão
Seção intitulada “License amarrada à sessão”A license_key vai do seu backend (não do browser). É a mesma license que o SDK usa no frontend, mas aqui ela é enviada server-side e amarrada à sessão:
- No
create, a Provvi valida que a license pertence ao mesmo cliente da suax-api-key(se não pertencer → 403). - Na captura, o status da license é revalidado (revogada / suspensa / expirada → captura rejeitada) — é a re-autenticação do fluxo.
Mantenha a x-api-key (alto privilégio) e a license_key ambas no backend. Só a license_key também aparece no frontend, por design do SDK.
Contrato HTTP completo (todos os endpoints, envelope de erro, TTLs): veja a Referência da API Web.
Uma foto por captura; você orquestra a quantidade
Seção intitulada “Uma foto por captura; você orquestra a quantidade”O Web SDK é uma câmera de uma foto por chamada: open() captura uma foto, devolve o resultado e para. Não há perfis nem sequência de N fotos guiada pelo SDK — você controla quantas fotos e com quais rótulos, chamando open() N vezes no mesmo token.
O rótulo de orientação de cada foto (o que o vistoriador deve enquadrar — ex.: “Chassi”) você passa no overlay: open({ overlay: { label } }). Ele é apresentado ao usuário e vai ao manifesto assinado como photo_label — evidência client-attested (ver Ciclo de captura).
Exemplo de backend: create-session.mjs
Seção intitulada “Exemplo de backend: create-session.mjs”Este módulo injeta a x-api-key no servidor e proxia o POST /web/sessions. Traz um handler estilo Express e um estilo Fetch (Vercel / Cloudflare / Deno). Cole no seu backend e adapte a rota:
/** * Provvi Web SDK — exemplo de backend para criação de sessão (Opção A). * * POR QUE ISTO EXISTE * ─────────────────── * A criação de sessão é autenticada pela **x-api-key** (pvv_api_*), que é uma * credencial de ALTO PRIVILÉGIO: quem a possui pode mintar sessões e, com cada * token, obter imagens assinadas com ICP-Brasil sob a SUA conta. Por isso ela * **nunca** pode ficar no frontend (qualquer visitante leria a chave no * view-source / aba de rede). A api key vive AQUI, no seu backend. * * Fluxo correto: * 1. Frontend chama o SEU endpoint (ex.: POST /api/create-session) — sem api key. * 2. Este handler injeta a x-api-key (de variável de ambiente) e proxia o * POST /web/sessions da Provvi. * 3. Devolve apenas o { token, expires_at } ao frontend. * 4. Frontend passa o token ao SDK: camera.open({ token }). * * A License key (pvv_*) é usada em DOIS pontos — a MESMA chave: * - No FRONTEND, para o SDK validar a licença na admissão (camera.open passa a * licenseKey; a validação é server-side via /validate). Pode ficar no frontend * — é desenhada para isso. * - AQUI no BACKEND, passada no create (POST /web/sessions) para AMARRAR a licença * à sessão (re-autenticação): o backend valida que a licença pertence ao * client da x-api-key (ownership) e o /web/ingest revalida o status (revoked/ * suspended/expired) na captura. Por isso o create envia license_key de uma env * server-side (PROVVI_LICENSE_KEY), não do body do frontend. * Só a x-api-key é EXCLUSIVAMENTE backend-only (credencial de alto privilégio). * * USO * ─── * Defina as variáveis de ambiente PROVVI_API_KEY e PROVVI_LICENSE_KEY (NUNCA * commitadas). Monte este handler na rota /api/create-session do seu servidor. Os * exemplos abaixo cobrem um handler genérico (req/res estilo Node/Express) e um * handler estilo Fetch (Vercel/Cloudflare/Deno). Adapte ao seu runtime. */
const PROVVI_SESSIONS_URL = process.env.PROVVI_SESSIONS_URL || 'https://sessions.provvi.com.br/web/sessions';
/** * Cria uma sessão Provvi server-side. Retorna o corpo da resposta da Provvi * (inclui `token` e `expires_at`). Lança em erro de configuração ou rede. * * @param {object} body Campos da sessão repassados pelo frontend (hoje só * `reference_id`). NUNCA confie em campos sensíveis vindos do cliente — * valide/whiteliste conforme seu caso. */export async function createProvviSession(body) { const apiKey = process.env.PROVVI_API_KEY; if (!apiKey) { throw new Error('PROVVI_API_KEY ausente no ambiente do backend — configure antes de usar.'); } // license_key amarrada à sessão no create, server-side. O create a exige — // fail-closed se ausente. const licenseKey = process.env.PROVVI_LICENSE_KEY; if (!licenseKey) { throw new Error('PROVVI_LICENSE_KEY ausente no ambiente do backend — configure antes de usar.'); }
const resp = await fetch(PROVVI_SESSIONS_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, // injetada no servidor — nunca exposta ao frontend }, // license_key vem do SERVER (env), não do body do frontend — o browser não a escolhe. body: JSON.stringify({ reference_id: body.reference_id, license_key: licenseKey, }), });
const data = await resp.json().catch(() => ({})); if (!resp.ok) { const err = new Error(data.error || `Provvi /web/sessions falhou (HTTP ${resp.status})`); err.status = resp.status; throw err; } return data; // { token, expires_at, ... }}
// ── Exemplo 1: handler genérico Node/Express (req, res) ──────────────────────//// app.post('/api/create-session', expressHandler);export async function expressHandler(req, res) { try { const data = await createProvviSession(req.body ?? {}); res.status(200).json({ token: data.token, expires_at: data.expires_at }); } catch (err) { res.status(err.status || 500).json({ error: err.message }); }}
// ── Exemplo 2: handler estilo Fetch (Vercel/Cloudflare/Deno) ─────────────────//// export default fetchHandler; // ou export const POST = fetchHandler;export async function fetchHandler(request) { try { const body = await request.json().catch(() => ({})); const data = await createProvviSession(body); return new Response( JSON.stringify({ token: data.token, expires_at: data.expires_at }), { status: 200, headers: { 'Content-Type': 'application/json' } }, ); } catch (err) { return new Response( JSON.stringify({ error: err.message }), { status: err.status || 500, headers: { 'Content-Type': 'application/json' } }, ); }}As duas variáveis de ambiente
Seção intitulada “As duas variáveis de ambiente”| Env var | O que é | Onde vive |
|---|---|---|
PROVVI_API_KEY |
Sua x-api-key (pvv_api_*) — alto privilégio |
Só no backend, nunca commitada |
PROVVI_LICENSE_KEY |
Sua license_key (pvv_*) — a mesma do frontend, injetada server-side no create |
Backend (e também no frontend, por design) |
Opcionalmente, sobrescreva PROVVI_SESSIONS_URL para apontar a outro ambiente; o default já é o endpoint de produção.
Erros do create
Seção intitulada “Erros do create”| HTTP | Causa | Corpo (exemplo) | O que fazer |
|---|---|---|---|
| 400 | Campo obrigatório ausente ou malformado (inclui license_key ausente/malformada) |
{ "error": "Payload inválido: ..." } |
Corrija o payload. Garanta license_key presente. |
| 401 | x-api-key ausente, inválida ou com prefixo errado |
{ "error": "API key inválida ou ausente" } |
Confira o header x-api-key (pvv_api_*). |
| 403 | license_key não pertence ao cliente da x-api-key |
{ "error": "License inválida para esta API key" } |
Use a license do mesmo cliente da api key. O corpo é único (anti-enumeração). |
| 429 | Limite de criação de sessões atingido | { "error": "Limite de criação de sessões atingido..." } |
Aplique backoff (espere e repita com intervalo crescente). Ver Configuração. |
| 503 | Indisponibilidade transitória (fail-closed) | — | Repita com backoff. Não trate como falha definitiva. |
Em 429 e 503, não repita em loop imediato: use backoff exponencial (ex.: 1s, 2s, 4s…) e desista após algumas tentativas.
Testar com curl
Seção intitulada “Testar com curl”Este comando cria uma sessão real e deve retornar 200:
curl -X POST https://sessions.provvi.com.br/web/sessions \ -H "Content-Type: application/json" \ -H "x-api-key: SUA_API_KEY" \ -d '{ "reference_id": "VIN-9BWZZZ377VT004251", "license_key": "SUA_LICENSE_KEY" }'Resposta esperada:
{ "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "expires_at": "..." }Checkpoint
Seção intitulada “Checkpoint”Você tem um { token, expires_at } vindo do seu backend, sem nunca expor a x-api-key ao browser. Esse token é o que o frontend entrega ao componente de câmera no próximo passo.
A garantia entregue no fim do fluxo é assinatura server-side ICP-Brasil A1 sobre os bytes recebidos (fraud deterrence) — não há âncora de proveniência de hardware device-side. Para cadeia de prova com âncora de hardware, use o SDK mobile.