Skip to content
PokerEnginePro
Developers

Built to be built on.
Typed, documented, versioned.

Every surface of the platform has a contract: generated types for the table protocol, an OpenAPI document for the back office, interfaces for every outside vendor and a REST API with webhooks for your own systems.

Contracts

One source of truth.
Generated, never retyped.

One contracts package with generated TypeScript types, checked for drift in CI.

The hub interfaces, events and payloads are written once, in C#. A generator reads them and writes the TypeScript every client imports — discriminated unions for events, string enums, camelCase — and the back office does the same from the admin API's OpenAPI document. A changed contract is a compile error in the client, not a surprise in production.

  • A player's own hole cards go to that player alone, and a contract test proves no spectator envelope ever carries a face-down card.
  • A test compares the committed TypeScript with the generator's output, so the types a client builds against are always the ones the server speaks.
How the contracts are generated and checkedTwo lanes. Player protocol: the C# Poker.Contracts package is read by a reflection-based generator that writes poker-contracts.ts, which the player clients import. Back-office API: the admin API serves one settled OpenAPI document, openapi-typescript compiles it to schema.d.ts, and the back office imports that as a typed client. Continuous integration fails the build when either generated file is stale.PLAYER PROTOCOLPoker.ContractsC# hub interfaces and eventsGeneratorreflection, no hand-written typespoker-contracts.tsdiscriminated unions, enumsPlayer clientsweb, mobile, botsread bywritesimported byBACK-OFFICE APIPoker.Admin.Apievery staff endpointopenapi.jsonone settled documentschema.d.tsopenapi-typescriptBack office UIa typed clientservescompiled toimported byCI fails on a stale file
contracts/ts/poker-contracts.ts
// GENERATED by Poker.Contracts.Generator — do not edit. Regenerate: dotnet run --project contracts/Poker.Contracts.Generator
// Protocol 1.1. Money values are integers in minor units; cards are two-character strings such as "As".

export const PROTOCOL_VERSION = "1.1" as const;
export const PROTOCOL_MAJOR = 1 as const;
export const TABLE_HUB_PATH = "/hubs/table" as const;
export const LOBBY_HUB_PATH = "/hubs/lobby" as const;
export const PROTOCOL_VERSION_HEADER = "X-Poker-Protocol" as const;

// …

export interface RoomEventBatch {
  roomId: string;
  serverTimeMs: number;
  events: RoomEventItem[];
}
The generated file the web client imports — its header and the protocol 1.1 batch type, quoted from the repository.

The table protocol

Documented for any client.
Two versions, one table.

The protocol specification: every hub method, event and error, versioned, for building any client.

A client announces the protocol it speaks when it connects; one that announces nothing, or something unknown, gets 1.0 — so old and new apps play at the same table and a store release is never a forced upgrade.

46%

fewer bytes per player

and 75% fewer messages with protocol 1.1, old and new clients at the same table

the protocol specification
/hubs/table?access_token=…&X-Poker-Protocol=1.1

interface RoomEventBatch {
  roomId: string          // every event in the batch is this room's
  serverTimeMs: number    // server time the burst was produced, Unix ms — use it for clock skew
  events: RoomEventItem[] // in order
}

interface RoomEventItem {
  seq: number             // the room sequence number, exactly as 1.0 numbers it
  handId?: string | null  // only on HandStarted; every later event belongs to that hand
  payload: RoomEventPayload
}
Quoted from the protocol specification: the connect URL, and the 1.1 batch a client applies item by item, in order.
Room events
Protocol 1.0One RoomEvent message per event
Protocol 1.1 · currentOne RoomEvents message per burst — everything one action set off
Envelope
Protocol 1.0roomId, seq, handId and serverTimeUtc on every event
Protocol 1.1 · currentroomId and serverTimeMs once per batch; seq per item; handId only on HandStarted
Your turn
Protocol 1.0A private YourTurn message
Protocol 1.1 · currentBuilt by the client from DecisionRequested and AwaitingAction, in the same batch
Who gets it
Protocol 1.0Any client that announces 1.0, nothing, or a version it does not know
Protocol 1.1 · currentClients that announce 1.1 when they connect

Hubs, methods and events

Two hubs. Every call typed.
Every event in order.

A client talks to two SignalR hubs: the table, where hands are played, and the lobby, where rooms and tournaments are found. Hand events on the wire are the engine's own events, projected per viewer — so the client and the hand-history replayer share one format.

Table hub

/hubs/table

The client calls · 20

  • JoinTable
  • LeaveTable
  • Sit
  • StandUp
  • SubmitAction
  • SitOut
  • RequestRebuy
  • UseTimeBank
  • RequestSnapshot
  • SendChat
  • Reauthenticate
  • JoinWaitlist
  • LeaveWaitlist
  • ChangeSeat
  • InviteUser
  • KickSeat
  • SetRoomPaused
  • CloseRoom
  • UpdateRoomConfig
  • AnswerRunIt

The server sends · 5

  • RoomEvents
  • RoomEvent
  • YourTurn
  • ActionRejected
  • RoomSnapshot

Lobby hub

/hubs/lobby

The client calls · 15

  • ListRooms
  • ListTemplates
  • Subscribe
  • Unsubscribe
  • CreateRoom
  • CreateRoomFromTemplate
  • ListTournaments
  • GetTournament
  • RegisterTournament
  • UnregisterTournament
  • TournamentRebuy
  • TournamentAddOn
  • ListTickets
  • SubscribeTournaments
  • UnsubscribeTournaments

The server sends · 3

  • RoomSummaryChanged
  • RoomRemoved
  • TournamentChanged

Sequencing and resync

Sequenced events, snapshot resync

Every room event carries the room's sequence number; a client that sees a gap stops, asks for a snapshot and carries on from it.

  • Each item's seq is the previous one plus one; chat, which is not sequenced, carries -1.
  • A private event — a player's own hole cards — arrives as the private version for its owner and the hidden one for everyone else, at the same seq.
  • After a reconnect, the snapshot carries the player's pending decision, so a turn is never lost.
  • Rejected actions come back as ActionRejected with the engine's reason, in their place in the order.
Sequence gap and snapshot resyncThe table hub sends room events numbered 41 and 42 to the client. Event 43 is lost. When event 44 arrives the client sees the gap, stops applying, and calls RequestSnapshot. The hub answers with a RoomSnapshot of the room, and the client carries on applying events from 45 in order.Table hub/hubs/tableClientany clientseq 41 ✓seq 42 ✓seq 43seq 44gap: expected 43 — stopRequestSnapshot(roomId)RoomSnapshotseq 45 ✓, then in order

Back-office API

OpenAPI, settled and checked.
A typed client on top.

A generated, checked-in OpenAPI document and typed client for every admin endpoint.

The admin API serves the same document on every request, and a test compares it with the checked-in copy the back office generates its types from. Staff sign in through your identity provider; every call is checked against the caller's permissions, and every command and sensitive read is audited.

  • Rooms
  • Players
  • Play-chip grants
  • Ledger
  • Reconciliation
  • Dead letters
  • Approvals
  • Audit
  • Settings
  • Rake
  • Games offered
  • Tournaments
  • Sit & Go lines
  • Social store
  • Hands
  • History and exports
  • Staff and roles
  • Live dashboard
  • Feature flags
backoffice/web/src/api/client.ts
/**
 * The typed client. Every shape here comes from `schema.d.ts`, which is generated from the admin API's
 * OpenAPI document, so the UI cannot drift from the API — a changed endpoint is a
 * compile error here rather than a surprise at runtime.
 */
export type Schemas = components['schemas']
export type MeView = Schemas['MeView']
export type RoomSummary = Schemas['RoomSummary']
export type RoomAdminView = Schemas['RoomAdminView']
The back office's typed client, quoted from the repository: every shape comes from the generated schema.

Vendor adapters

Bring your own vendors.
The game never notices.

Payments, identity, geolocation, sanctions, AML, self-exclusion, messaging and ads behind interfaces with contract tests — swap vendors without touching the game.

Every outside dependency sits behind an interface the platform owns. Adapters are configured per operator, and each implementation is held to the same contract tests — so a new payment provider or KYC vendor is an adapter, not a fork.

IPaymentProcessor · the vendor adapter interfaces
/// <summary>
/// Deposits and withdrawals. The adapter moves money in the outside world; the ledger entry
/// is written by the platform when the provider confirms, never before.
/// </summary>
public interface IPaymentProcessor
{
    /// <summary>Which methods and currencies this adapter handles, so the client only offers what works.</summary>
    IReadOnlyCollection<PaymentMethod> SupportedMethods { get; }

    Task<PaymentResult> Deposit(Guid tenantId, PaymentRequest request);

    Task<PaymentResult> Withdraw(Guid tenantId, PaymentRequest request);

    /// <summary>Verifies and parses a provider webhook; the platform reconciles it against its own record.</summary>
    Task<PaymentResult?> ParseWebhook(Guid tenantId, string payload, IReadOnlyDictionary<string, string> headers);
}
The payments adapter interface, quoted from the platform's vendor adapter contracts.

Payments

IPaymentProcessor

Deposits, withdrawals and provider webhooks. The ledger entry is written when the provider confirms, never before.

Identity

IIdentityVerification

Submit a player's identity check and read its status; real-money rooms, deposits and withdrawals gate on it.

Geolocation

IGeolocation

Where a player connects from — country, region, VPN and proxy flags — for jurisdiction gating.

Sanctions

ISanctionsScreening

Sanctions and PEP screening. An empty answer means no match, never “not checked”.

AML

IAmlMonitoring

Transaction monitoring, fed by the platform as money moves, without ever holding up a player.

Self-exclusion

ISelfExclusionRegistry

National self-exclusion registers, checked before a real-money session starts.

Messaging

IEmailSender · ISmsSender · IPushSender

Email, SMS and push notifications, each sent on behalf of the operator they belong to.

Ads

IAdProvider

Rewarded video and interstitials. A reward is paid on the network's server-to-server verification, never on the client's word.

Integrate

Your systems, connected.

For the CRM, the data warehouse and the apps you already run: a partner API with webhooks, the engine reference and the guides to put them together.

01

Partner API and webhooks

A REST API for operators' own systems, with keys per tenant — scoped, rotated with an overlap and rate-limited — and signed webhooks that retry with back-off, keep a dead-letter list and can be replayed. An OpenAPI contract is published. It is an add-on.

02

SDKs

Typed contracts and OpenAPI documents are published today. Packaged client SDKs for the web and mobile are not built yet.

03

The engine API reference

The engine API reference documents the pure engine's public surface — the rules, the deck, the hand configuration and the calls a room makes to drive a hand.

04

Install, operations and integration guides

Guides for installing, operating and integrating the platform: an evaluation install, production step by step with a check after each step, day-two operations, and a hands-on tutorial — written so a team can install it on its own.

Read the contracts.
Then watch them run.

A walkthrough for your engineers: the protocol on a live table, the admin API behind the back office, and where your vendors plug in.