Documentation / Spécification du protocole / v1
Spécification du protocoleRéférence API
Documentation / Spécification du protocole / v1
STABLE · DEPUIS V1

Spécification du protocole

Protocole ouvert de vérification des billets émis par des instances indépendantes. Chaque déploiement client agit comme un issuer autonome ; n'importe quel scanner implémentant ce protocole peut vérifier les billets de n'importe quel issuer, sans intégration bilatérale préalable.

Référence API

Cette page décrit le protocole lui-même. Pour les endpoints, paramètres, codes de réponse et exemples de requêtes, consultez la référence API →

§ 01

Vue d'ensemble

Ce protocole définit comment un scanner valide un billet émis par un issuer indépendant. Les issuers ne se coordonnent pas entre eux et n'ont pas besoin d'une autorité centrale.

Le protocole est conçu pour quatre objectifs :

  • Vérification hors ligne de l'authenticité des billets via signatures cryptographiques
  • État de check-in en ligne avec cohérence forte pour empêcher le double-scan
  • Indépendance des issuers : les issuers ne se coordonnent pas entre eux
  • Rotation des clés sans invalider les billets déjà émis
Version actuelle : 1

Tous les payloads et endpoints incluent un identifiant de version. Les changements cassants requièrent une nouvelle version majeure. Issuers et scanners peuvent supporter plusieurs versions simultanément pendant les transitions.

§ 02

Terminologie

Issuer

Instance de déploiement qui crée et gère les billets. Un issuer par client.

Scanner

Application cliente qui valide les billets à l'entrée de l'événement.

Ticket

Credential unique et signé donnant accès à un événement spécifique.

Check-in

Acte de marquer un billet comme utilisé à l'entrée.

Payload

Données signées encodées dans le QR code du billet.

§ 03

Cryptographie

Ed25519 RFC 8032 64 octets · Web Crypto API

L'algorithme de signature est Ed25519 (RFC 8032). Les signatures de 64 octets sont suffisamment compactes pour tenir dans un QR code aux côtés des données du payload, et la vérification est rapide avec un support natif dans la Web Crypto API.

Cycle de vie des clés

Chaque issuer génère une paire de clés au provisioning. La clé privée est stockée dans les secrets du Worker et ne quitte jamais l'issuer. La clé publique est publiée au endpoint well-known. Plusieurs clés publiques actives peuvent coexister pendant les fenêtres de rotation.

Identifiant de clé (kid)

Chaque clé publique possède un identifiant court et unique : les 8 premiers caractères hexadécimaux du SHA-256 de la clé publique brute. Les payloads référencent le kid pour que les scanners sachent quelle clé utiliser pour la vérification.

§ 04

Format du payload QR

Le QR code contient un payload JSON compact encodé en base64url, suivi de la signature. Le préfixe tkt1. identifie la version du protocole et distingue les QR codes de billets des autres codes qu'un scanner pourrait rencontrer.

Format
tkt1.<base64url(payload_json)>.<base64url(signature)>
Taille maximale

Le contenu total du QR doit rester sous ~400 caractères pour rester scannable sur des caméras bas de gamme en faible luminosité. Évitez de remplir inutilement les champs ev et hn.

Structure JSON du payload

Payload JSON
{
  "v": 1,
  "iss": "https://tickets.example.com",
  "kid": "a1b2c3d4",
  "tid": "01HXXX...ULID",
  "eid": "evt_01HXXX...ULID",
  "ev": {
    "name": "Gala Concert",
    "date": "2026-06-15T20:00:00Z",
    "venue": "Royal Hall"
  },
  "tt": "VIP",
  "hn": "J. Smith",
  "iat": 1745000000,
  "exp": 1765000000
}
Champ Description
v
integerrequis

Version majeure du protocole (actuellement 1).

iss
stringrequis

URL de base de l'issuer. Forme canonique : HTTPS, sans slash final. Utilisé pour la découverte.

ex : https://tickets.example.com
kid
stringrequis

Identifiant de la clé de signature (8 premiers hex de SHA-256 de la clé publique).

tid
stringrequis

Identifiant du billet (ULID recommandé, unique au sein de l'issuer).

eid
stringrequis

Identifiant de l'événement (ULID recommandé, unique au sein de l'issuer).

ev
objectrequis

Métadonnées de l'événement pour affichage hors ligne : name (string), date (ISO 8601 UTC), venue (string). Informatif uniquement.

tt
stringoptionnel

Type de billet, pour affichage (ex : "VIP", "Standard").

hn
stringoptionnel

Nom d'affichage du porteur. Garder court ; pas de PII au-delà du nom d'affichage.

iat
integerrequis

Timestamp Unix d'émission (secondes).

exp
integerrequis

Timestamp Unix d'expiration (secondes). Généralement fixé à un délai raisonnable après la fin de l'événement.

Signature

La signature est calculée sur la séquence exacte d'octets du payload encodé en base64url (avant le second séparateur .), en utilisant la clé privée Ed25519 de l'issuer. Les scanners vérifient en recalculant la signature sur les mêmes octets.

§ 05

Issuer discovery

Chaque issuer DOIT publier un document de découverte à l'adresse suivante :

GET {iss}/.well-known/ticket-issuer.json
Réponse · 200 OK
{
  "protocol_version": 1,
  "issuer": "https://tickets.example.com",
  "name": "Nom commercial du client",
  "keys": [
    {
      "kid": "a1b2c3d4",
      "alg": "Ed25519",
      "public_key": "base64url-encoded-raw-32-byte-public-key",
      "valid_from": "2026-01-01T00:00:00Z",
      "valid_until": null
    }
  ],
  "endpoints": {
    "check_in": "https://tickets.example.com/api/v1/tickets/check-in",
    "status": "https://tickets.example.com/api/v1/tickets/{tid}/status",
    "scanner_enroll": "https://tickets.example.com/api/v1/scanners/enroll"
  },
  "supported_versions": [1]
}
  • keys PEUT contenir plusieurs entrées pendant la rotation. Les scanners doivent accepter toute clé non expirée dont le kid correspond au payload.
  • valid_until: null signifie actuellement active sans expiration programmée.
  • Les scanners DEVRAIENT mettre ce document en cache avec un TTL d'au plus 1 heure, et DEVRAIENT le re-récupérer si la vérification échoue avec un kid inconnu.
§ 06

QR de setup du scanner

Les scanners s'enrôlent dans un événement en scannant un QR code de setup à durée limitée généré par l'interface admin de l'organisateur. Le QR de setup contient un token d'enrollment que le scanner échange, à la première utilisation, contre une clé API longue durée scopée à un ou plusieurs événements.

Échange en deux étapes

Le flux d'enrollment utilise un échange token → clé API plutôt que d'embarquer directement une clé API dans le QR. Le QR de setup est ainsi à usage unique et à durée limitée ; un QR photografié ou divulgué devient inutile une fois consommé.

Format du Setup QR
tsetup1.<base64url(setup_json)>

Le préfixe tsetup1. distingue les QR de setup des QR de billet (tkt1.).

Setup JSON
{
  "v": 1,
  "iss": "https://tickets.example.com",
  "enroll_url": "https://tickets.example.com/api/v1/scanners/enroll",
  "token": "st_01HXXX...opaque-enrollment-token",
  "eid": "evt_01HXXX...ULID",
  "event_name": "Gala Concert",
  "gate_id": "north",
  "expires_at": "2026-06-16T02:00:00Z"
}
Champ Description
v
integerrequis

Version majeure du protocole (actuellement 1).

iss
stringrequis

URL de base de l'issuer. Le scanner DOIT récupérer le document de découverte depuis {iss}/.well-known/ticket-issuer.json.

enroll_url
stringrequis

URL exacte pour l'échange d'enrollment. DOIT correspondre au scanner_enroll du document de découverte.

token
stringrequis

Token d'enrollment opaque et imprévisible (≥ 128 bits d'entropie). Usage unique.

eid
stringrequis

ID de l'événement pour lequel ce QR de setup enrôle le scanner.

event_name
stringoptionnel

Nom affiché à l'opérateur pour confirmation. Le nom officiel vient de l'issuer après enrollment.

gate_id
stringoptionnel

Identifiant de porte. Si présent, la clé API est scopée à cette porte.

expires_at
stringrequis

Timestamp ISO 8601 UTC après lequel le token est invalide.

Capacités du scanner

Un scanner PEUT détenir des enrollments actifs pour plusieurs événements simultanément. Cela supporte les lieux multi-scènes, les événements consécutifs et les festivals. Chaque enrollment ajoute un événement à l'ensemble actif du scanner sans remplacer les enrollments précédents.

Cycle de vie de la clé API

  • Scope : chaque clé API est liée à exactement un événement (et optionnellement une porte). Un scanner inscrit pour plusieurs événements détiendra plusieurs clés API.
  • Expiration : les clés API expirent au timestamp expires_at retourné dans la réponse d'enrollment (généralement fin de l'événement + marge). Les clés expirées sont rejetées avec UNAUTHORIZED.
  • Révocation : les organisateurs peuvent révoquer une clé à tout moment depuis l'interface admin.
  • Pas de refresh : si une clé API expire en cours d'événement, le scanner doit se ré-enroller via un nouveau QR de setup.
  • Rotation : pour remplacer une clé compromise, l'organisateur révoque l'ancienne clé et émet un nouveau QR de setup.
§ 07

Flux de vérification

Quand un billet est scanné, le scanner DOIT auto-détecter l'événement correspondant en lisant le champ eid du payload vérifié. L'opérateur ne sélectionne pas manuellement un événement. Voici l'ordre de résolution :

1. Signature

Parser le payload QR et vérifier la signature avec les clés publiques en cache de l'issuer dans iss. Si la signature ne vérifie pas : rejeter avec invalid_signature.

2. Temps

Valider les bornes temporelles iat/exp. Si expiré : rejeter avec expired.

3. Enrollment

Chercher les enrollments actifs par la combinaison (iss, eid) du payload.

4. Match

Si un enrollment correspondant existe : sélectionner la clé API, vérifier la compatibilité de porte, puis appeler l'endpoint de check-in.

5. Pas de match

Si aucun enrollment ne correspond : rejeter localement avec not_enrolled. Afficher le nom de l'événement depuis ev.name.

6. Inconnu

Si l'iss est totalement inconnu du scanner : rejeter avec unknown_issuer.

§ 08

Cohérence et double-scan

La prévention du double-scan est appliquée par l'issuer via un Durable Object scopé par événement. Toutes les écritures de check-in pour un événement donné sont sérialisées à travers ce DO, garantissant qu'exactement un check-in réussit même sous requêtes concurrentes depuis plusieurs portes.

Cache local recommandé

Les scanners DEVRAIENT maintenir un cache local des tid récemment scannés (dernières 24h) et rejeter les doublons côté client avant de solliciter le réseau. Cela réduit la charge et permet le rejet instantané des doublons évidents même hors ligne.

§ 09

Mode hors ligne

Les scanners DEVRAIENT pouvoir fonctionner avec une connectivité intermittente. Voici l'ordre de traitement pour chaque scan :

1

Vérifier la signature localement avec les clés publiques en cache pour l'issuer dans iss.

2

Valider les bornes temporelles iat/exp.

3

Chercher l'enrollment correspondant pour (iss, eid) dans le stockage local. Si aucun : rejeter localement.

4

Vérifier le cache local des scans antérieurs de ce tid. Si trouvé : rejeter comme doublon.

5

Tenter l'appel API de check-in. Sur succès : accepter et enregistrer localement. Sur échec réseau : mettre en file d'attente avec l'idempotency_key, accepter de manière optimiste, retenter en arrière-plan.

6

Quand la connectivité revient, vider la file. L'idempotence de l'issuer garantit l'absence d'incohérence.

Les issuers DOIVENT accepter des requêtes de check-in avec des timestamps scanned_at dans le passé (jusqu'à une fenêtre raisonnable, ex : 48h) pour supporter les scans hors ligne en file d'attente.

§ 10

Sécurité

  • Vérification de signature obligatoire avant toute confiance dans le contenu du payload. Ne jamais afficher les noms des porteurs ou les infos d'événement sans vérifier la signature d'abord.
  • Validation temporelle : les scanners DOIVENT rejeter les billets dont exp est dans le passé au moment du scan, et DEVRAIENT avertir sur des timestamps iat dans le futur de manière invraisemblable.
  • Validation de l'URL issuer : le champ iss est une URL. Les scanners DOIVENT imposer HTTPS et DEVRAIENT restreindre à une liste de confiance de domaines issuers si le contexte l'exige.
  • Stockage des clés API : les applications scanner DOIVENT stocker les clés API dans le stockage sécurisé de la plateforme (Keychain, Keystore).
  • Rate limiting : les issuers DOIVENT limiter le débit des endpoints de check-in par credential scanner pour prévenir les abus.
  • TLS : tous les endpoints DOIVENT être servis en TLS 1.2+. Les redirections HTTP vers HTTPS ne suffisent pas pour les appels API.
  • Protection contre le replay : l'idempotency_key prévient le replay accidentel. Le replay intentionnel avec une nouvelle clé sera détecté par l'état du tid côté serveur et retournera already_used.

Sécurité de l'enrollment

  • Confidentialité du QR de setup : le QR de setup est un bearer credential jusqu'à consommation. Il DEVRAIT être généré juste avant de distribuer les appareils au staff, affiché uniquement sur des écrans de confiance, et ne pas être photografié ou partagé par chat/email.
  • Entropie du token : les tokens DOIVENT avoir au moins 128 bits d'entropie provenant d'un RNG cryptographiquement sécurisé.
  • Rate limiting : l'endpoint scanner_enroll DOIT être rate-limité par IP et par token.
  • TLS obligatoire : les scanners DOIVENT rejeter tout QR de setup dont iss ou enroll_url n'est pas HTTPS.
  • Confirmation opérateur : le scanner DEVRAIT afficher le event_name, la date et le nom de l'issuer, et demander confirmation explicite avant d'appeler l'endpoint d'enrollment.
  • Piste d'audit : les issuers DEVRAIENT logger chaque tentative d'enrollment (succès et échec).
§ 11

Versioning et évolution

  • Le champ v dans les payloads et le champ protocol_version dans le document de découverte portent la version majeure.
  • Les changements additifs mineurs (nouveaux champs optionnels) n'incrémentent pas la version majeure. Les scanners DOIVENT ignorer les champs inconnus.
  • Les changements cassants incrémentent la version majeure. Les issuers DEVRAIENT continuer à accepter la version précédente pendant une période de transition raisonnable (ex : 6 mois).
Dernière mise à jour : 20 mai 2026