A hotel came to us with a task that sounds simple but in practice runs straight into network architecture: a guest books a room online — and the system has to issue them a key card at the turnstile by the entrance itself, and cancel it automatically at check-out. No receptionist manually adding every guest to the access-control panel.
Below is how we connected a cloud booking system to physical equipment on the hotel’s local network, what pitfalls we hit, and why the solution rolls out to other properties without pain.
Why this isn’t “just an HTTP request”
The two parts of the system live in different worlds:
- The PMS booking system — in the cloud (QloApps on top of PrestaShop), reachable from the internet.
- Access controllers — Hikvision turnstiles on the hotel’s local network, behind a router and NAT, with no public IP.
You can’t reach hardware on a private network directly from the cloud: inbound ports are closed, there’s no public address. Forwarding ports at every property is both a security hole and a constant support headache (hotels change providers, routers, IPs). We needed a solution that works behind any NAT and requires nothing from the hotel’s network.
Architecture: three links and one key idea
We split the integration into three parts — a cloud module, a gateway service, and a local agent:
[PMS in the cloud] [Reception PC, local network]
Booking module Local agent (Windows service)
│ │
▼ ▼
Gateway service ─── WSS (agent dials out) ──► Access controller
(our backend) (turnstile / card reader)
The key idea is inverting the connection. Instead of the cloud reaching into the hotel, the local agent opens the outbound channel itself over WebSocket Secure (port 443). To the router this is ordinary outbound HTTPS traffic — nothing to open or forward, it works behind any NAT out of the box. When a booking comes in, the gateway writes a task to the queue and pushes it down the already-open channel to the right agent.
The lifecycle of one booking
- The guest pays for the booking in the PMS → the module puts an
issuetask on the gateway’s queue (room number, period, which zones are accessible). - The gateway finds the right hotel’s agent among the open WSS connections and sends the task.
- The agent translates it into calls to the controller: registers the guest, generates/binds a card, and schedules the action for the check-in and check-out dates.
- The controller confirms — the agent returns a status, the gateway updates the database record, and a “card issued” line appears in the log.
- At check-out (or when the check-out date arrives) — the mirror scenario,
revoke: the card is cancelled and the guest is removed from the controller.
The administrator can see and configure all of a hotel’s equipment in one section — a list of turnstiles with their addresses, access zones and status:

Each turnstile has its own network details, role (entry/exit), permission groups, and which hotel zones the card unlocks:

What turned out to be tricky
Channel stability and an acknowledged queue
A WSS connection has to stay alive for hours and survive network drops. The agent reconnects on its own, and tasks aren’t lost: they sit in a database queue with statuses, and the gateway keeps retrying delivery until the agent confirms completion (ack). If the reception PC was switched off overnight — in the morning the agent comes back up, picks up everything that piled up, and works through it in order. Every operation is idempotent, so a retry never creates duplicate cards.
ISAPI isn’t the REST everyone’s used to
The agent talks to the controller in its native protocol — ISAPI over HTTP Digest: its own authentication, endpoint-specific JSON structures, its own error codes, and hundreds of pages of protocol documentation. We wrapped all of that “kitchen” behind a clean internal contract, so the rest of the system doesn’t need to know the controller’s details and just works with simple issue / revoke / status commands. If a different model or vendor shows up tomorrow, only the adapter in the agent changes — not the whole system.
A card lives exactly as long as the stay
A card has to appear exactly for the stay period and disappear right after check-out — not a day early, not a day late. We tied issuing and revoking to booking events in the PMS and log every operation: the administrator can always see which card was issued to whom and for what period, and can revoke it manually ahead of time if needed.
Updates without a site visit
The agent is a Windows service on the reception PC. So we wouldn’t have to visit every hotel for a new version, we built a self-update mechanism: the agent pulls updates itself over the same secure channel and restarts as a service.
Security
- No inbound ports on the hotel’s network at all — only outbound WSS on 443.
- The agent authenticates to the gateway with a token; each hotel only sees its own tasks.
- Controller credentials are stored on the hotel’s side, not floating around in the cloud.
- A full log of issued/revoked cards — for audits and resolving disputes.
Stack
- Gateway: Node.js (WebSocket server + REST for the PMS module), queue and log in MySQL, ready to scale to many hotels via pub/sub.
- Agent: Node.js, packaged as a Windows service; ISAPI Digest to the access controller; self-updating.
- PMS: QloApps / PrestaShop — integrated via a module on the booking system’s side.
- Testing: end-to-end scenarios across the whole chain — booking → gateway → agent → controller → card record → status back into the database.
Result
The hotel got a fully automatic cycle: online booking → card at the turnstile → automatic cancellation at check-out, with no manual reception work and no hole in the property’s network security. The solution is reusable — connecting a new hotel just means putting the agent on the reception PC; the rest of the infrastructure is already there.
Need to integrate a booking system, CRM or website with physical equipment — turnstiles, locks, tills, PRRO? Get in touch — we’ll take a look at your case.

