Pular para o conteúdo

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.

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 backend
SEU backend ──POST /web/sessions (com x-api-key)────▶ Provvi
SEU backend ◀──── { token, expires_at } ───────────── Provvi
Frontend ◀──── { token, expires_at } ──────────────── SEU backend

O frontend recebe apenas { token, expires_at }. Essa é a regra de ouro: a x-api-key mora só no servidor.

URL base (API Gateway):

https://sessions.provvi.com.br

Headers:

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 esses dois campos. O token tem validade de 30 minutos e é single-use.

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 sua x-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).

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' } },
);
}
}
Env var O que é Onde vive
PROVVI_API_KEY Sua x-api-key (pvv_api_*) — alto privilégio 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.

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.

Este comando cria uma sessão real e deve retornar 200:

Terminal window
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": "..." }

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.


← Como funciona · Próximo → O componente