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>
  • OSP1 indica 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 que crypto.subtle.sign da 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" }
  ]
}
versionSempre 1 nesta versão.
issuerA origem HTTPS de quem assinou o passe, ex.: https://timeclock.kiwi. As chaves são encontradas a partir dela.
keyIdQual chave do emissor o assinou.
holderIdIgual 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, expiresAtHorá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, lastNameO nome.
companyPara quem trabalha, se escolheu compartilhar.
phoneNúmero de celular, de preferência em formato internacional.
emailEndereço de e-mail.
vehicleRegistrationPlaca do veículo em que veio.
documentsCartõ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-passportSite Safe passport
public-liability-insuranceSeguro de responsabilidade civil
electrical-registrationRegistro de eletricista
plumbing-gasfitting-registrationRegistro de encanamento, gás ou drenagem
forklift-licenceLicença de empilhadeira (F endorsement)
first-aid-certificateCertificado de primeiros socorros
working-at-heightsTrabalho em altura
confined-spacesEspaços confinados
construction-induction-cardCartão de integração em construção (White Card)
cscs-cardCSCS 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

  1. 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.
  2. Busque chaves só na origem HTTPS do próprio emissor e guarde-as em cache para verificar um passe offline.
  3. Mostre os dados e o domínio do emissor, e deixe o visitante confirmar antes de salvar qualquer coisa.
  4. 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.
  5. Você pode aceitar só passes de emissores confiáveis, ou aceitar qualquer emissor HTTPS e mostrar "Verified by <domain>".
  6. Nunca peça ao app do titular mais do que o passe contém. O titular escolhe o que compartilhar.

5. Regras para apps

  1. Gere um novo passe pelo menos a cada minuto enquanto estiver na tela.
  2. Deixe o titular escolher quais dados opcionais incluir, a cada vez ou como uma escolha salva que ele possa mudar.
  3. 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.

Estamos sempre felizes em ajudar.
Entre em contato e faremos o possível para que nosso sistema de controle de ponto funcione para você.