Claiming signs
A "claim" is the moment a physical sign becomes part of an event. Before the claim, the sign is just a kiosk showing a QR code on a wall. After the claim, the dashboard knows about it, the mobile app can drive it, and the assigned content URL renders.
This page covers the workflow you'll use 90% of the time, the alternative ("Link") workflow used when signs are pre-registered, and the edge cases that come up at live events.
Acknowledged handshake — when enabled in your workspace
This page describes the claim flow once your workspace has the
link/claim handshake enabled (backend env flag LINK_HANDSHAKE_ENABLED).
Pre-handshake workspaces use an older optimistic-write flow with no
automatic rollback — see the
Pre-handshake workspaces sidebar at the
bottom of this page.
Before you claim
A sign is ready to be claimed when:
- It's powered on and finished its boot sequence
- The kiosk is showing the QR claim screen — a centered QR code with a 6-character short code below it
- The sign has reached the backend (the QR screen renders a NetworkChecklist showing the connection state)
- The dashboard's Unclaimed Signs list shows the device
If the QR screen never appears or the sign doesn't show up under Unclaimed Signs, see Troubleshooting → Sign won't claim.
What's in the QR code
The QR encodes a small JSON payload:
{
"type": "displaysync-sign",
"version": 1,
"signId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"shortCode": "A1B2C3"
}
signIdis a UUID v4 generated on the device's first boot and stored locally — it never changes for the life of that install.shortCodeis a 6-character uppercase derivative of the signId. It's there as a manual-entry fallback when the QR is unscannable (smudged, glare, weird lighting). You can type it into the mobile app instead of scanning.
How a claim is committed
Both Flow A (mobile claim) and Flow B (link to pre-created sign) use the same backend handshake. The kiosk has to acknowledge that it can actually load the URL before the backend commits the claim — no more silent failures where the dashboard says "claimed" but the wall is showing a 404.
The flow:
- Operator initiates the claim (mobile scan or dashboard claim form). Backend creates a short-lived
pendingSignRedis record (TTL-bounded), mintssignId+shortCode, returns to the operator. - Operator submits (selects org, event). Backend issues a request with a fresh
requestIdto the kiosk over WebSocket: "load this URL, claim with these credentials." - Kiosk acks via its
LinkAckHandlerservice. If the URL loads successfully, kiosk emitslink_ackwith success. If load fails (URL unreachable, page-load error, timeout), kiosk emitslink_ackwith failure + a structured sub-reason. - Backend commits or rolls back. On ack-success, the Sign DB row is created/updated and the operator sees "claimed" in the UI. On ack-failure, backend rolls back: clears
pendingSign, returns the error to the operator. - Idempotency: a duplicate
requestId(the operator retried because of a network blip) replays the cached ack rather than re-running the load.
When the handshake fails, the kiosk's LinkAckHandler clears currentUrl and org info from the local config, then the kiosk reverts to the QR setup screen. The operator gets a structured error. No partial state — either the claim succeeds end-to-end or both sides return to pre-claim.
For the four new error codes you may see during a failed handshake, see Errors and rollback below.
Flow A — Claim from the mobile app (most common)
This is the on-site workflow.
- Open the mobile app and sign in.
- From the Events tab, tap the event you're claiming the sign to.
- On the event's detail screen, tap the bottom-right FAB labeled "Add a sign to this event" (the icon is a QR-code-scan glyph). Camera permission is requested the first time.
- Scan the QR code on the kiosk. The app auto-detects the payload.
- Fill in:
- Name — what the sign should be called in the dashboard (e.g., "Main Lobby East")
- Location — free-form text (e.g., "Hall B, near registration")
- Webpage URL (optional) — the URL to display immediately after claim
- Tap Claim.
What happens server-side:
- Backend validates the short code matches the signId (prevents typo'd manual claims).
- Verifies the sign isn't already claimed.
- Creates a Sign record in the org and event you picked.
- Sends a
sign:claimedevent to the kiosk over the WebSocket.
What happens on the kiosk:
- Within ~1 second, the QR screen disappears.
- If you provided a URL, it loads. If not, the sign goes idle (a "ready" splash screen) until you assign content.
Flow B — Link to a pre-created sign (fleet workflows)
When you're managing dozens of signs, it's often cleaner to pre-create the records in the dashboard (with names, locations, and content already set) and let on-site techs link the physical devices to them. This is mostly a matter of who's doing the data entry — the technical effect on the device is identical.
- In the dashboard, create signs ahead of time: Event → Signs → Add Sign, fill in name + location + content, leave the device fields empty. The sign appears in the dashboard with a status of Unlinked.
- On the day, the on-site tech opens the mobile app, navigates to the unlinked sign in the event, and taps Link to device.
- Scan the kiosk's QR.
- The mobile app calls
POST /signs/:id/linkinstead ofPOST /signs/claim— the existing Sign record gets the device fields written to it, keeping its pre-set name, location, and content.
The sign on the wall behaves identically to Flow A — same sign:claimed event, same content load.
Errors and rollback
When the handshake fails, the operator sees one of four structured errors. Each comes with a rollback to the QR screen on the kiosk side and an error message on the operator side.
| Error code | HTTP | What it means to the operator |
|---|---|---|
device_no_ack | 504 | Sign didn't respond. Check that it's online and try again. |
device_load_failed | 400 | Sign couldn't load the page. Sub-reason indicates url_unreachable, url_load_failed, or sign_timeout. |
sign_not_connected | 400 | Sign isn't connected. Power-cycle and retry. |
request_in_progress | 409 | Another claim is in progress for this sign. Wait a moment and retry. |
For full meanings + diagnostic paths, see Error codes and Sign won't claim.
When to pre-create vs claim on-site
Pre-create when the show plan is locked in advance and you know exact sign placements ("Lobby West", "Ballroom B Stage Right"). On-site claim when signs are mobile, get reassigned, or the run-of-show is fluid — claim, rename later. Either workflow can be used freely within the same event.
Where the claim is stored
A successful claim writes data in two places:
| Location | What gets stored |
|---|---|
| Backend database (Sign record) | deviceSignId, macAddress, shortCode, name, location, status, settings |
| Kiosk (Electron Store) | organizationId, organizationName, eventId, eventName, currentUrl |
Both halves of the pair survive reboots. The kiosk-side data is what makes the sign re-attach to its event after a power cycle without needing to be re-claimed.
Unclaiming and re-claiming
To detach a sign from an event:
- Dashboard: the sign's detail page → Settings → Unregister.
- The backend wipes the device fields off the Sign record and emits
sign:unregisteredto the kiosk. - The kiosk clears its local org/event/URL data and returns to the QR screen.
- The sign is now claimable to any event again.
Unregister is non-destructive on the dashboard side — the Sign record's name, location, and history stick around as Unlinked, ready to be re-linked or fully deleted.
Common edge cases
Wrong event
You claimed the sign to "Conference 2026 — Main Hall" but it should have been "Conference 2026 — Breakout Room". Unregister the sign from the wrong event, then re-claim it to the right event. (The kiosk returns to the QR screen on unregister, so re-claiming is a fresh scan from the mobile app.)
Replacement hardware (sign died, swap a new unit in)
The dead unit's record is still in the database with the old deviceSignId and macAddress. The replacement kiosk has a fresh signId and MAC, so it'll appear as a brand-new Unclaimed Sign.
- From the dashboard, unregister the dead sign (or delete it if you don't need its history).
- Boot the replacement, scan its QR from the mobile app, claim to the same event with the same name. The new device gets the same role, dashboard URL, and team visibility — only the underlying identity is fresh.
If you used Flow B (pre-created records), even simpler: the existing Sign record is still there. Unlink the dead device, then link the replacement.
Cloned image identity collision
If two cloned kiosks come up on the network with the same signId (because identity wasn't reset before capturing the image), the desktop sign's clone-detection logic catches it.
On every boot, the kiosk checks: does my stored MAC match any of my current network interfaces? If not, it assumes it's a fresh clone, clears its local identity, and generates a new signId on next start. The QR screen then reappears with a fresh code, ready to be claimed.
So: nothing for you to do at the dashboard level if this happens. The kiosk recovers itself within one reboot. The only manual fix needed is on the dashboard if a stale Sign record was created — delete it.
NIC switch (Ethernet → Wi-Fi)
The kiosk stores its MAC at first boot and never updates it. So when a tech unplugs the Ethernet and the device falls back to Wi-Fi, the stored MAC still matches one of the device's NICs — clone-detection doesn't fire, identity is preserved, the sign just keeps working.
NIC replacement (rare)
Replacing a thin client's network adapter does change the MAC. Clone-detection sees the stored MAC matches no current interface and treats it as a clone — clears identity, regenerates. The sign comes back as a new Unclaimed entry. Re-claim it (or use the swap-replacement workflow above).
This is acceptable behavior for the rare case it happens. Don't replace NICs on running fleet hardware unless you're prepared to re-claim.
Pre-handshake workspaces
Legacy claim flow — handshake disabled
Workspaces without LINK_HANDSHAKE_ENABLED use the optimistic-write
claim flow — claims commit immediately on the backend without device
ACK. If the kiosk fails to load the URL, the dashboard still shows
the sign as claimed and there is no automatic rollback — the
operator has to retry from the kiosk side or unregister and re-claim.
Once LINK_HANDSHAKE_ENABLED is on, the flow above applies and
rollback is automatic.
What's next
- Assigning content — the URL on the sign is just data; you can change it anytime after claim
- Mobile app — the on-site workflow for claiming dozens of signs in a few hours
- Remote control — once a sign is claimed, what you can tell it to do