Open Site Pass

Un estándar abierto de código QR para registrarse en lugares de trabajo. Cualquier app puede mostrar un pase y cualquier quiosco puede leerlo.

Versión 1, noviembre de 2026. Uso gratuito bajo CC BY 4.0.

Los contratistas se registran cada semana en un sitio distinto, y todos piden los mismos datos: nombre, empresa, móvil, vehículo, tarjeta Site Safe. Open Site Pass les permite llevar esos datos en una app y entregarlos mostrando un código QR. Cualquier app puede mostrar un pase y cualquier quiosco de registro puede leerlo, sea quien sea el fabricante.

Antes de escribir esto buscamos un estándar existente. Los productos de registro de visitantes con pases QR tienen cada uno su propio formato, que solo funciona dentro de ese producto. Los estándares generales de identidad (W3C Verifiable Credentials, OpenID for Verifiable Presentations, permisos de conducir móviles ISO 18013-5) necesitan una app de cartera y un intercambio de varios pasos, más de lo que necesita una tableta en la entrada de un sitio. Por eso este es un formato pequeño que cualquier lenguaje puede leer y comprobar con su criptografía integrada.

1. Qué contiene el código QR

El código QR contiene texto sin formato en tres partes unidas por puntos:

OSP1.<pass>.<signature>
  • OSP1 indica que es un Open Site Pass, versión 1.
  • <pass> es el pase en JSON, UTF-8 y luego base64url sin relleno.
  • <signature> es una firma ECDSA P-256 con SHA-256 sobre el texto ASCII de <pass> (exactamente como aparece en el código QR), en la forma r ‖ s de 64 bytes y luego base64url sin relleno. Es la forma que produce crypto.subtle.sign de Web Crypto.

2. El pase

{
  "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" }
  ]
}
versionSiempre 1 en esta versión.
issuerEl origen HTTPS de quien firmó el pase, p. ej. https://timeclock.kiwi. Sus claves se encuentran a partir de esto.
keyIdQué clave del emisor lo firmó.
holderIdEl mismo para una persona con un emisor, y distinto con cualquier otro emisor. Permite a un sitio reconocer a un visitante que vuelve. No debe ser un número de empleado, un correo ni nada que lo identifique en otro lugar.
issuedAt, expiresAtHoras ISO 8601. Un pase debe durar alrededor de un minuto y no más de 15 minutos, para que una foto de él pronto no sirva.
firstName, lastNameSu nombre.
companyPara quién trabaja, si decidió compartirlo.
phoneNúmero de móvil, idealmente en formato internacional.
emailCorreo electrónico.
vehicleRegistrationMatrícula del vehículo en el que llegó.
documentsTarjetas y certificados: cada uno tiene type (de la lista siguiente) y, opcionalmente, number, issuer y expiresOn (AAAA-MM-DD).

Obligatorios: version, issuer, keyId, holderId, issuedAt, expiresAt, firstName. Todo lo demás es opcional y lo elige el titular cada vez.

Tipos de documento

site-safe-passportSite Safe passport
public-liability-insuranceSeguro de responsabilidad civil
electrical-registrationRegistro de electricista
plumbing-gasfitting-registrationRegistro de fontanería, gas o drenaje
forklift-licenceLicencia de carretilla elevadora (F endorsement)
first-aid-certificateCertificado de primeros auxilios
working-at-heightsTrabajo en altura
confined-spacesEspacios confinados
construction-induction-cardTarjeta de inducción en construcción (White Card)
cscs-cardCSCS card

Para proponer otro tipo, ponte en contacto más abajo. Los quioscos conservan los documentos de tipos que no conocen, pero no los asocian a nada.

3. Claves del emisor

Los emisores publican sus claves públicas en <issuer>/.well-known/open-site-pass/keys.json:

{
  "keys": [
    {
      "keyId": "2026-11-3fa9c1",
      "publicKey": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" }
    }
  ]
}

Cada publicKey es un JWK, que Web Crypto puede cargar directamente. Sigue publicando una clave al menos un día después de dejar de firmar con ella. Las claves de Timeclock.Kiwi están en https://timeclock.kiwi/.well-known/open-site-pass/keys.json.

4. Reglas para quioscos

  1. Comprueba la firma con la clave del emisor y que el pase no haya caducado. Permite hasta 2 minutos de margen por diferencias de reloj.
  2. Obtén las claves solo del origen HTTPS del propio emisor y guárdalas en caché para poder comprobar un pase sin conexión.
  3. Muestra los datos y el dominio del emisor, y deja que el visitante confirme antes de guardar nada.
  4. Trata los datos de los documentos como lo que introdujo el titular, no como verificados. Si tu sitio necesita verificarlos, haz que una persona vea la tarjeta real.
  5. Puedes aceptar solo pases de emisores de confianza, o aceptar cualquier emisor HTTPS y mostrar "Verified by <domain>".
  6. Nunca pidas a la app del titular más de lo que lleva el pase. El titular elige qué compartir.

5. Reglas para apps

  1. Genera un pase nuevo al menos cada minuto mientras esté en pantalla.
  2. Deja que el titular elija qué datos opcionales incluir, cada vez o como una opción guardada que pueda cambiar.
  3. Nunca incluyas nada que el titular no haya elegido, como su empleador, ubicación o identificadores internos.

6. Comprobar un pase en JavaScript

Esto es todo lo que necesita un quiosco, usando solo la criptografía integrada del 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;
}

Uso con Timeclock.Kiwi

Cualquiera con sesión iniciada en la app de Timeclock.Kiwi puede mostrar un pase desde Mi pase de sitio, y todos los quioscos de registro de Timeclock.Kiwi los leen, incluidos los pases de otras apps que siguen este formato.

Siempre estamos encantados de ayudar.
Ponte en contacto con nosotros y haremos todo lo posible para que nuestro sistema de control de asistencia se adapte a ti.