Open Site Pass
Um padrão aberto de código QR para registrar entrada em locais de trabalho. Qualquer app pode mostrar um passe e qualquer quiosque pode lê-lo.
Versão 1, novembro de 2026. Uso gratuito sob CC BY 4.0.
Prestadores registram entrada em um local diferente toda semana, e cada um pede os mesmos dados: nome, empresa, celular, veículo, cartão Site Safe. O Open Site Pass permite levar esses dados em um app e entregá-los mostrando um código QR. Qualquer app pode mostrar um passe e qualquer quiosque de registro pode lê-lo, seja quem for o fabricante.
Antes de escrever isto, procuramos um padrão existente. Os produtos de registro de visitantes com passes QR têm cada um seu formato, que só funciona dentro do produto. Os padrões gerais de identidade (W3C Verifiable Credentials, OpenID for Verifiable Presentations, carteiras de motorista móveis ISO 18013-5) precisam de um app de carteira e de uma troca em várias etapas, mais do que um tablet na entrada de um local precisa. Por isso este é um formato pequeno que qualquer linguagem consegue ler e verificar com sua criptografia integrada.
1. O que há no código QR
O código QR contém texto simples em três partes unidas por pontos:
OSP1.<pass>.<signature> OSP1indica que é um Open Site Pass, versão 1.<pass>é o passe em JSON, UTF-8, depois base64url sem preenchimento.<signature>é uma assinatura ECDSA P-256 com SHA-256 sobre o texto ASCII de<pass>(exatamente como aparece no código QR), na forma r ‖ s de 64 bytes, depois base64url sem preenchimento. É a forma quecrypto.subtle.signda Web Crypto produz.
2. O passe
{
"version": 1,
"issuer": "https://timeclock.kiwi",
"keyId": "2026-11-3fa9c1",
"holderId": "b7f3c2e1a94d0c55e1f3b2a8",
"issuedAt": "2026-11-01T09:42:00.000Z",
"expiresAt": "2026-11-01T09:43:00.000Z",
"firstName": "Sam",
"lastName": "Taylor",
"company": "Harbour Electrical Ltd",
"phone": "+64215550142",
"vehicleRegistration": "QKT482",
"documents": [
{ "type": "site-safe-passport", "number": "SS-482913", "expiresOn": "2027-03-31" }
]
} | version | Sempre 1 nesta versão. |
|---|---|
| issuer | A origem HTTPS de quem assinou o passe, ex.: https://timeclock.kiwi. As chaves são encontradas a partir dela. |
| keyId | Qual chave do emissor o assinou. |
| holderId | Igual para uma pessoa com um emissor, e diferente com qualquer outro emissor. Permite que um local reconheça um visitante que retorna. Não deve ser número de funcionário, e-mail ou algo que identifique a pessoa em outro lugar. |
| issuedAt, expiresAt | Horários ISO 8601. Um passe deve durar cerca de um minuto e não mais de 15 minutos, para que uma foto dele logo perca o valor. |
| firstName, lastName | O nome. |
| company | Para quem trabalha, se escolheu compartilhar. |
| phone | Número de celular, de preferência em formato internacional. |
| Endereço de e-mail. | |
| vehicleRegistration | Placa do veículo em que veio. |
| documents | Cartões e certificados: cada um tem type (da lista abaixo) e, opcionalmente, number, issuer e expiresOn (AAAA-MM-DD). |
Obrigatórios: version, issuer, keyId, holderId, issuedAt, expiresAt, firstName. Todo o resto é opcional e escolhido pelo titular a cada vez.
Tipos de documento
site-safe-passport | Site Safe passport |
|---|---|
public-liability-insurance | Seguro de responsabilidade civil |
electrical-registration | Registro de eletricista |
plumbing-gasfitting-registration | Registro de encanamento, gás ou drenagem |
forklift-licence | Licença de empilhadeira (F endorsement) |
first-aid-certificate | Certificado de primeiros socorros |
working-at-heights | Trabalho em altura |
confined-spaces | Espaços confinados |
construction-induction-card | Cartão de integração em construção (White Card) |
cscs-card | CSCS card |
Para propor outro tipo, entre em contato abaixo. Os quiosques mantêm documentos de tipos desconhecidos, mas não os associam a nada.
3. Chaves do emissor
Os emissores publicam as chaves públicas em <issuer>/.well-known/open-site-pass/keys.json:
{
"keys": [
{
"keyId": "2026-11-3fa9c1",
"publicKey": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" }
}
]
} Cada publicKey é um JWK, que a Web Crypto pode carregar diretamente. Continue publicando uma chave por pelo menos um dia depois de parar de assinar com ela. As chaves do Timeclock.Kiwi estão em https://timeclock.kiwi/.well-known/open-site-pass/keys.json.
4. Regras para quiosques
- Verifique a assinatura com a chave do emissor e se o passe não expirou. Permita até 2 minutos de margem para diferenças de relógio.
- Busque chaves só na origem HTTPS do próprio emissor e guarde-as em cache para verificar um passe offline.
- Mostre os dados e o domínio do emissor, e deixe o visitante confirmar antes de salvar qualquer coisa.
- Trate os dados dos documentos como informados pelo titular, não como verificados. Se o local exigir verificação, peça a uma pessoa que veja o cartão real.
- Você pode aceitar só passes de emissores confiáveis, ou aceitar qualquer emissor HTTPS e mostrar "Verified by <domain>".
- Nunca peça ao app do titular mais do que o passe contém. O titular escolhe o que compartilhar.
5. Regras para apps
- Gere um novo passe pelo menos a cada minuto enquanto estiver na tela.
- Deixe o titular escolher quais dados opcionais incluir, a cada vez ou como uma escolha salva que ele possa mudar.
- Nunca inclua nada que o titular não escolheu, incluindo empregador, localização ou IDs internos.
6. Verificar um passe em JavaScript
Isto é tudo de que um quiosque precisa, usando só a criptografia integrada do navegador:
async function readPass(text, fetchKeys) {
const [prefix, body, signature] = text.split('.');
if (prefix !== 'OSP1') throw new Error('Not a site pass');
const pass = JSON.parse(atob(body.replace(/-/g, '+').replace(/_/g, '/')));
const keys = await fetchKeys(pass.issuer); // GET <issuer>/.well-known/open-site-pass/keys.json
const jwk = keys.find(k => k.keyId === pass.keyId)?.publicKey;
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
const fromB64 = s => Uint8Array.from(atob(s.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
const ok = await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, fromB64(signature), new TextEncoder().encode(body));
const now = Date.now(), slack = 2 * 60 * 1000;
if (!ok || Date.parse(pass.expiresAt) + slack < now) throw new Error('Not valid');
return pass;
} Como usar com o Timeclock.Kiwi
Qualquer pessoa conectada ao app Timeclock.Kiwi pode mostrar um passe em Meu passe de local, e todo quiosque de registro do Timeclock.Kiwi os lê, incluindo passes de outros apps que seguem este formato.