DeviceIntegrationsModule is the server half of the Device Hub. On-site hub agents hold a WebSocket open to it; staff screens (POS, KDS, kiosk, the Hubs admin) call its HTTP routes to send work to hardware and subscribe to what the hardware reports. Installing and pairing a hub is covered in setting up a hub; this page is the API.
Datatypes: device_hub (one per hub box, keyed by data.name) and device_config (one per peripheral).
Hubs
/device-integrations/hubs/:sk/api-keyJWT/device-integrations/hubs/live-statesJWT/device-integrations/hubs/:hubName/stateJWT/device-integrations/hubs/:hubName/pingJWT/device-integrations/hubs/:hubName/discover-pathsJWT/device-integrations/hubs/:hubName/endpointsJWTapi-key— mints a newhub_…key, stores only its SHA-256 hash withapiKeyMintedAt, and returns the cleartext once. Re-minting invalidates the old key, so the agent is locked out until it is reconfigured.live-states—{ hubName: { online, connectedAt, version } }for every hub with an open socket, read from the gateway's in-memory map rather than the database.state— one round trip for a Diagnose panel: thehubrecord (status,lastSeen,lastError,ipAddress,apiKeyMintedAt,endpointsReported), theliveview (online, connected since, version, endpoints) and thedevicesconfigured on that hub.ping— times a round trip to the agent:{ ok, online, latencyMs, error? }.discover-paths— asks the agent to list candidate device files (HID readers, USB printers, USB-serial) so the UI can offer a picker. Linux only; other platforms return none.endpoints— pushes peripheral config to the agent, which saves it and reloads. Body{ action: 'merge', endpoints: [{ name, kind, capabilities, config }] }upserts by name;{ action: 'remove', names }removes;replaceis also accepted.
Calling a device
/device-integrations/adaptersJWT/device-integrations/adapters/configuredJWT/device-integrations/callJWT/device-integrations/pageJWTcall takes { capability, data }, where capability is page · print · drawer · charge · weigh · display · kds. The target is resolved as:
?adapter=consoleor?adapter=sms-pager— sent straight to that adapter, no lookup.?device=<name>— thatdevice_config.- Otherwise the first active
device_configwith that capability, narrowed by?servicePoint=and?location=.
A device with connection: 'remote' is sent to its hubName / endpointName over the hub socket; adapterName: 'sms-pager' sends a Twilio SMS; console just logs and succeeds (for testing). With no matching device, a page falls back to the SMS pager when Twilio is configured; anything else answers 503. Each call stamps lastUsedAt on the device, and lastError on failure. The result is { ok, ref?, data?, error? }.
page is the shortcut for paging a guest: body { phone, pagerNumber, message, alertType }, capability implied. The SMS pager needs a phone number.
Events from hubs
/device-integrations/eventsJWTHub agents push events — scanner reads, drawer opened, printer errors — and the server fans them out per org. Each message is { kind, hubName, endpointName, data, ts }; a hub-status event is emitted whenever a hub connects or drops. Filter with ?hub= and ?kinds= (comma-separated); orgid may be passed as a query parameter because EventSource cannot set headers. The stream pings every 25 seconds.
Browsers can instead subscribe over socket.io on the /device-events namespace with auth: { token, orgId, hub? } and listen for event. The token must be a user of that org; sockets are dropped if the account is locked.
The hub socket
Agents connect to /ws/hub (a raw WebSocket) with x-hub-name, x-hub-key and x-orgid headers, or hubName, apiKey and orgid query parameters. The key is checked against the stored hash; a bad key closes the socket with code 4001. One connection per hub — a reconnect closes the previous socket.
On connect the hub is marked online. Its hello reports its endpoints and version, and the server creates a device_config for each endpoint it has not seen, leaving operator edits alone. heartbeat refreshes lastSeen and per-endpoint status. On disconnect the hub goes offline and its endpoint statuses are cleared.