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> OSP1indica 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 producecrypto.subtle.signde 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" }
]
} | version | Siempre 1 en esta versión. |
|---|---|
| issuer | El origen HTTPS de quien firmó el pase, p. ej. https://timeclock.kiwi. Sus claves se encuentran a partir de esto. |
| keyId | Qué clave del emisor lo firmó. |
| holderId | El 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, expiresAt | Horas 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, lastName | Su nombre. |
| company | Para quién trabaja, si decidió compartirlo. |
| phone | Número de móvil, idealmente en formato internacional. |
| Correo electrónico. | |
| vehicleRegistration | Matrícula del vehículo en el que llegó. |
| documents | Tarjetas 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-passport | Site Safe passport |
|---|---|
public-liability-insurance | Seguro de responsabilidad civil |
electrical-registration | Registro de electricista |
plumbing-gasfitting-registration | Registro de fontanería, gas o drenaje |
forklift-licence | Licencia de carretilla elevadora (F endorsement) |
first-aid-certificate | Certificado de primeros auxilios |
working-at-heights | Trabajo en altura |
confined-spaces | Espacios confinados |
construction-induction-card | Tarjeta de inducción en construcción (White Card) |
cscs-card | CSCS 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
- 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.
- Obtén las claves solo del origen HTTPS del propio emisor y guárdalas en caché para poder comprobar un pase sin conexión.
- Muestra los datos y el dominio del emisor, y deja que el visitante confirme antes de guardar nada.
- 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.
- Puedes aceptar solo pases de emisores de confianza, o aceptar cualquier emisor HTTPS y mostrar "Verified by <domain>".
- Nunca pidas a la app del titular más de lo que lleva el pase. El titular elige qué compartir.
5. Reglas para apps
- Genera un pase nuevo al menos cada minuto mientras esté en pantalla.
- Deja que el titular elija qué datos opcionales incluir, cada vez o como una opción guardada que pueda cambiar.
- 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.