“Real-time” is one of those terms that gets used to mean either “live” or “fast” depending on whoever is selling. In our domain it means something specific: a state change at any device must propagate to every other interested device before a human notices the gap.
That distinction sounds academic until you watch a poker floor run with and without it.
The friction without it
Picture a busy Friday night. A dealer at table 4 vacates a seat, a player has left for dinner break. In a non-real-time system, here’s what happens next:
- The dealer marks the seat as on-break in their tool.
- The change writes to the database.
- The next time the floor manager refreshes their dashboard (could be 15 seconds, could be 3 minutes), the seat appears as on-break.
- The player app polls every minute or two; the seat eventually shows up as available.
- A queueing player who’s been refreshing their phone every 10 seconds sees the seat open and walks to the table. The dealer, looking at their seat map from 40 seconds ago, doesn’t know the player is incoming.
The whole loop is anywhere from 30 seconds to several minutes of stale state. In low-traffic rooms it’s fine. In a high-volume room with a queue, it’s the difference between losing a seat to a no-show and not.
The architecture
Our backend is built on ASP.NET Core, so a managed WebSocket layer was the natural choice for real-time. The decisions that matter:
-
One hub, many groups. A single
PokerHubconnection per device, with the device joining tenant-scoped, room-scoped, and table-scoped groups. Events fan out only to devices that need them. -
User-targeted events for invitations. The “your seat is ready” message is the highest-criticality event in the system. We send it both to the player’s user-targeted real-time channel AND as a push notification, belt-and-suspenders. The full-screen invitation modal in the player app is the most-watched UI in the product.
-
Reconnect logic that resyncs state, not just connection. When a tablet drops wifi mid-shift and reconnects, just re-establishing the socket isn’t enough. The device’s view of the world is stale. After every reconnect, automatic or manual, we run a baseline sync: pause inbound events, rejoin groups, fetch fresh data, resume events. If we don’t, you get phantom seats: the dealer sees seat 4 as occupied because their socket reconnected before their state did.
-
Idempotent events. Network conditions are messy. Events get retried. The dealer should be able to record the same pot twice and have the system understand it. Idempotency keys on every state-changing operation; client-side dedup; idempotency-protected endpoints on the API. None of this is glamorous. All of it matters.
What “real-time” actually changes
The interesting effect isn’t faster updates. It’s a different unit of work.
In a polling-based world, operations think in batches: “every 30 seconds, the floor reconciles.” Decisions get queued for the next refresh. Behavior is bursty.
In a real-time world, operations are continuous. The floor manager sees the queue update as it happens. They make a call to skip a player who’s gone offline based on a state that’s truly current, not 30 seconds old. The player who joined the queue two minutes ago and is watching their phone sees their position move from 3 to 2 to 1 to “your seat is ready” without ever refreshing.
That continuity is what changes the operational unit. A floor that runs in continuous time runs differently from one that runs in 30-second batches. It’s quieter, fewer “wait, is the seat open or not?” interruptions. It’s faster, seats fill in the time it takes a player to walk from the lounge to the table. And it’s more accurate, there’s no window where two devices disagree about state.
Why a managed WebSocket layer specifically
We considered raw WebSockets, MQTT, and a custom long-polling fallback. The managed-WebSocket route won on three counts: it negotiates the best transport per client (WebSocket where supported, falls back gracefully), it has first-class group management (we lean on this hard), and it has battle-tested reconnect semantics. The integration with our ASP.NET Core stack is good enough that we get tenant scoping for free via the auth claim our API verifies on every request.
You can build all of this from scratch. We didn’t, because the boring infrastructure shouldn’t be the interesting work. The interesting work is the floor itself.
The takeaway
If you’re evaluating real-time claims in poker management software, the question to ask isn’t “do you have WebSockets.” Everyone says yes. The question is: what happens when a tablet’s wifi drops mid-shift? If the vendor doesn’t have a clean answer involving reconnect baseline state, you’re going to lose some hands to phantom seats. We’ve seen it. We built around it. It matters.