QR check-in passes
Issue a pass, what the QR contains, the scan window, duplicate scans, and the manual fallback when the camera cannot be used.
A check-in pass is the door’s fast path into a booking: an opaque secret that a scanner turns into a recorded arrival. Issuing is a host act; scanning is a door act; the scan itself commits the very same arrival command the desk uses by hand. In this pilot there is no camera capture — the pass travels as text, and the door types or pastes it.
Who does what
| Act | Role | Where |
|---|---|---|
| Issue or rotate a pass | Host and above | panel “Issue / rotate a check-in pass” |
| Scan and commit the arrival | Door and above | panel “Scanner — scan and commit arrival” |
| Record the arrival without a pass | Host or door | the arrival forms on Arrivals, seating & visits |
Issuing and scanning both need a staff session — there is no public scan path — and the scanner’s venue permission is checked before any pass lookup, so an unauthenticated holder of a valid pass learns nothing.
Issue a pass
Open the panel “Check-in passes / scanner (DEV-026 panel)” at /app/panels, enter the reservation id and press “Issue pass”. The success line carries everything, exactly once: “pass v2 for DG-4KQ7X2AM — secret (shown once): … — scans until …”.
- One live pass per booking. Issuing rotates: every earlier pass is revoked in the same stroke, so the freshly issued pass is the only live one, and its version number goes up.
- Confirmed bookings only. A cancelled, no-showed or pending request takes no pass (
RULE_VIOLATION). - The raw secret is shown exactly once. It is never logged; the audit trail records the version and the expiry, never the value.
- The pass scans until the booked end. Its expiry is the booking’s own end instant.
- No email delivers the pass. Getting it to the guest — printed, written down, shown on a screen — is a human step. A lost or compromised pass is not a problem: issue a fresh one and the old one dies instantly.
Treat the shown-once secret like a room key: whoever holds it can check the party in during the scan window. That is all it can do — it opens no guest portal and reveals no booking details.
What the QR payload contains
The payload is the pass secret: 32 random bytes encoded base64url — exactly 43 characters. Nothing else rides in it: no name, no booking reference, no venue. The database stores only an HMAC digest of the secret, so a copied database yields no usable passes, and every scan is checked by hashing the presented token the same way and comparing digests.
The scan window
A pass scans from 60 minutes before the booked start until the booked end:
| Moment | Result |
|---|---|
| Earlier than start − 60 min | refused — CHECKIN_PASS_WINDOW |
| Inside the window | the check-in proceeds |
| After the booked end | refused — CHECKIN_PASS_WINDOW |
The refusal names the window (both instants), so staff can tell the guest exactly when scanning works. CHECKIN_PASS_WRONG_VENUE means the pass was issued by a different venue: send the guest there — nothing about the other booking is revealed.
What a scan does
The scanner form takes the scanner’s venue id, the pass token, the arriving count (1 up to the remaining guests), an optional device id (recorded with the scan) and a request key. The server then checks, in order: your permission over the scanner’s venue; the pass itself — malformed, unknown, revoked and expired all answer the same TOKEN_INVALID, with no oracle to tell them apart; the booking status (CONFIRMED or RULE_VIOLATION); the scan window; and finally the arrival, committed through the same command the desk uses.
Outcomes are explicit — a scan never silently adds attendance:
| Outcome | Meaning | What you see |
|---|---|---|
ARRIVED |
the arrival was recorded | “Checked in.” with the picture: code, name, arrived x/y, remaining, visit state and version |
DUPLICATE |
nothing recorded again; the current status is replayed | “Already checked in — nothing recorded again.” with the same picture |
A scan reads DUPLICATE when the same request key is replayed or when the party is already complete. A count beyond the remaining guests on a not-yet-complete party is refused with RULE_VIOLATION — enter the number actually at the door.
Manual entry — there is no camera in V1
Plainly: camera capture is not implemented in V1. The release device matrix records the camera scan as not implemented and the physical-tablet walkthrough as pending hardware; manual pass entry is the implemented path. In practice:
- Type or paste the 43-character pass token into the “Pass token (the QR payload)” field — in V1 that text field is the scanner; there is no camera to photograph a QR with.
- Or skip the pass entirely. The arrival forms take the count without any pass: find the booking on the service board and record the arrival there (Arrivals, seating & visits).
Offline: the scanner refuses rather than guessing
Offline — check-ins are online-only. While the device has no connection the commit button is disabled and the panel states: “Offline — check-ins are online-only. Nothing has been committed or queued; no success is shown without the server.”
There is no local write, no queue and no optimistic “admitted” state: success is rendered only from a server-confirmed result, and the server stamps the arrival time — a device cannot inject its own timestamp into a check-in. If a submit slips through a dropped connection it fails honestly and can simply be repeated once the connection is back. The general offline behaviour is described in Offline & installable app.
What the guest holds
The booking confirmation screen tells the guest: “Show this code at the venue.” — the DG- confirmation code. That code alone is enough for staff to find the booking and record the arrival by hand; the pass is the faster path when one was issued. The guest portal at /b manages the booking — reconfirm or cancel — but plays no part in check-in.
Pilot posture
- Manual entry is the real path during this pilot; nothing on these screens claims camera scanning.
- Single-market pilot evidence only: one approved country and its listed capabilities do not constitute global support; unlisted countries and capabilities stay disabled.
- Unusual refusals and their fixes are collected in Troubleshooting by scenario and Error codes & fixes.