Platform · Future

One contract,
every game

For the future, not how it works today. This is the engine room we are building for later: launch, session, round, settlement, recall and replay, with failure modes we designed before anything failed.

Endpoints behind a round
8

two auth schemes that are never interchangeable

Licence checks per launch
10

in one atomic read, before a URL is issued

Steps in a settled bet
6

not a single transaction, because the wallet is on somebody else's network

Games on the same contract
8

and it does not change shape for the next one

This is the future, not today

We are a game studio. Today our games reach players through an established server partner, and the operator keeps the player, the wallet and the licence. Everything on this page is the platform we are building for later. It exists as code and runs in development, but it is not what we sell, and no operator is connected to it.

  1. Our titles

    built and measured in the engine room below

  2. A server you run

    an established remote gaming server your team already works with

  3. Your players

    on your site, through your wallet, under your licence

Our titles reach your players through an established remote gaming server. We chose that route on purpose. Operators do not book integration dates with a studio they have not heard of, and a new studio needs shelf space first, and fast. So our games arrive the way the rest of your catalogue does, through a server your team already runs against.

This page covers what sits behind that: the engine room. Every game we have built, and every one we build next, runs on one contract: 8 endpoints, one licence chain, one settlement saga and one wallet protocol. A new title is a row in a catalogue and a directory in our repository. It does not need a second certification, a second security review or a second copy of anything. Today the contract serves 8 games, and it will not change shape for the hundredth.

It is also why a title from us is priced the way it is, and why the tenth arrives at the same quality as the first. The endpoints, licence chain, sequence and wallet protocol below are read from the same source as our own API reference, which is transcribed from the running server with a file and line number on every entry.

The contract, end to end

Eight endpoints. Two authentication schemes that are never interchangeable: the launch call is server-to-server under an operator credential, and everything else carries a session token held by a browser.

EndpointAuthWhat it is forClause
POST/v1/launchoperatorThe operator's backend asks for a one-time URL to put in an iframe. Every licence question is answered here and nowhere else.None
POST/v1/session/openopenThe client burns the launch ticket and receives everything it needs to draw the game truthfully: the resolved config, the market's play rules, the full description, the balance, and the commitment to the seed.GLI-19 §4.4.1 · Ksa KS.09.28..36 (the player can read all of it before staking)
POST/v1/round/betsessionOne press of the button. Six steps, three of them transactions and two of them network calls into the operator's wallet.GLI-19 §4.3.3 · UKGC RTS 14D (speed of play) · Ksa KS.09.26 (no stake while an outcome is unresolved)
POST/v1/round/stepsessionA cash-out, a card, a pot. It stores the player's DECISIONS and never the round's state, so the engine resolves from the seed and the whole decision list every time.GLI-19 §4.16.3(b): a game requiring player input must return to the state it was interrupted in
GET/v1/round/recall?limit=5sessionThe player's own recent rounds, replayable, with everything needed to check them once the seed is out.GLI-19 §4.14 · §4.14.2 (what it must show) · §4.14.3 (the last fifty feature events)
POST/v1/round/resumesessionThe player has been debited and the round never reached a final state. This finishes it from the ORIGINAL session's seed and nonce, under the original idempotency key.GLI-19 §4.16.3 · Ksa KS.09.08 · KS.09.26
POST/v1/session/closesessionThe other half of the commitment. The server seed is published here and never before.GLI-19 §3.2 (the commit-reveal scheme the RNG evidence rests on)
GET/v1/verification?fresh=1openDoes what is running hash to what was approved? The cached answer is free; ?fresh=1 re-hashes the deployment.GLI-19 §2.3.2 · §B.2.6

Credential

Operator HMAC credential

Authorization: GWG-HMAC-SHA256 keyId=…,ts=…,nonce=…,sig=…

The signature covers the scheme, the method, the path, the timestamp, the nonce and a sha256 of the body: six lines joined with newlines. The timestamp window is 30 seconds either way, the nonce is single-use, and the credential can be pinned to source addresses in credential_ip_rules. CORS deliberately never answers a preflight for /v1/launch: a browser has no business making this call, and answering would suggest otherwise.

The operator's backend. Server to server, never a browser.

Credential

Session token

Authorization: Bearer <sessionToken>

An HS256 JWT, hand-rolled in thirty readable lines. It carries sid, the session id; oat, the session's opened_at in epoch milliseconds; and op, the operator id. `oat` is there because gameplay.game_sessions is partitioned by it, so a lookup without it probes every partition on the hottest read in the system (ADR-012). The header is compared byte for byte before the payload is touched, which is what closes the alg=none confusion.

The game client, in the iframe, for the life of one session.

Credential

None

Two endpoints take no credential. /v1/session/open is authenticated by the single-use launch ticket in its body rather than by a header, and burns it on first use. /v1/verification is deliberately open: it answers what the control program hashes to, which is a question a regulator or an operator's auditor is entitled to ask without being given an account.

Anyone who can reach the deployment.

Ten questions, one read, before a URL exists

Every licence question is answered by one atomic read in the database, never by the client, the session or the first bet. Because it is one read, nothing can change between two of the checks.

One read, in that order, with the widest fact first so the first no is the most useful one. A refusal raises a machine code in the database’s own DETAIL field, which the server maps to an error code. Nothing branches on the message text, and no other code path in the server may answer any of these ten questions on its own.

Revocation is checked separately, each time a session loads, so a suspension takes effect on the next bet and not only at the next launch. The ticket that comes back is single-use and lives sixty seconds, and we store only its hash, so a leak of our database hands out no live launches.

The bet, in six steps

The wallet is a network call into somebody else's system. Holding one database transaction open through another company's outage is how you run out of connections, so the bet is split into short steps.

  1. Our databaseone short transaction, nothing in flight

    round(open) + bet tx(pending) + idempotency key

    Our intent, written down before anything leaves the building. The round, the bet transaction, and the client's idempotency key all land in one short transaction, under two locks taken in a fixed order.

    A campaign free round runs this same step, key and bet row both, with a zero amount confirmed at insert. Skipping them used to be an optimisation, and it was a double-payout path: with no stored key a retried free round replayed nothing and consumed a second grant, and resume invented a key the wallet had never seen.

  2. Your walleta call out of our building and into yours

    wallet debit

    POST /bet into the operator's wallet, carrying our idempotency key and our transaction_id. Their answer is one of three things, and the third one is the point of the whole design.

  3. Our databaseone short transaction, nothing in flight

    confirm / fail / leave pending

    ok → the bet row is confirmed and the operator's reported balance is stored on it (§2.8.2(d) wants the funds available at the start of play, and we are not the wallet). rejected → the bet fails and the player is told why. unknown → nothing is undone: the round and its pending transaction are left in place on purpose.

    Voiding on unknown would risk voiding a bet the operator actually took. Reconciliation retries the same key to find out which it was.

  4. The gamepure arithmetic with no clock, no I/O and no network

    engine.play()

    The game, resolved from the session's committed seed and the round's nonce. No I/O, no clock, no globals. Given the same stream position, bet, config and selection it produces the same result forever. That is the only reason a round from two years ago can be replayed.

    A stepped game is handed the whole decision list every time and replays the round from the seed. Nothing is carried between calls, so a paused round is exactly as replayable as a spin.

  5. Your walleta call out of our building and into yours

    win tx(pending) → credit → confirmed

    Only when the round paid something. The credit's key is the bet's key with `:win` appended, so the two movements of one round are distinguishable and each is independently idempotent.

    A credit that does not confirm leaves the round OPEN with a pending win. Settling here would record a win we never delivered.

  6. Our databaseone short transaction, nothing in flight

    settle round + persist events

    status settled, win_minor, draws_consumed, and every event written in one transaction. The UPDATE asserts it matched exactly one row.

    That assertion is not defensive habit. A partition-key mismatch made this update match nothing and say so only by returning zero, so the API reported `settled` while the ledger kept the round open with the player already debited.

  7. If the power goes out between any two of them

    A round `open` with a `pending` transaction.

    Deliberate, and detectable: ops.check_gameplay_integrity() finds them; the count is wired to an alert and shows on the World screen as `rounds_stranded`. Reconciliation retries the wallet call with the SAME idempotency key to discover what actually happened. A round left open is recoverable. A round settled on a debit that never landed is not.

The wallet is never ours, and the protocol is four calls

We hold no player funds and have nowhere to put them. Every stake and every win is a call into the operator's own wallet, keyed by a transaction id we generate. Whoever serves our games, this is how the money moves.

  • POST /balance

    Session open, and whenever a balance has to be shown.

    4 fields in · answers { "balance": "125000" }

  • POST /bet

    A stake is taken. Always, once per round.

    9 fields in · answers { "balance": "124900", "transactionRef": "your-ref-771" }

  • POST /win

    A payout is made. Only for a round that had a /bet.

    the /bet envelope + roundClosed · answers { "balance": "128300", "transactionRef": "your-ref-772" }

  • POST /rollback

    A /bet must be undone because the round could not be completed.

    7 fields in · answers { "balance": "125000" }

An answer has three arms, not two

The most important line in the contract is the boundary between rejected and unknown. A 402 is a decision. A 5xx is not, and neither is a timeout.

  • ok

    { status: "ok"; balanceMinor: bigint; operatorTxRef?: string }

    The money moved and we know the balance afterwards.

  • rejected

    { status: "rejected"; reason: WalletRejection; operatorTxRef?: string }

    The money definitely did NOT move. We never retry this.

  • unknown

    { status: "unknown"; detail: string }

    We cannot tell. The round stays open, the same key is retried with backoff, and the player is not offered another spin. Collapsing this into `rejected` is how a player gets charged twice.

We never hold player funds. Every stake and every win is a call into the operator's own wallet, keyed by a transaction id we generate, and every call is idempotent on that key: a retry gets the original outcome and is never applied twice. When a server partner carries our games, the call is theirs to make, and it has to satisfy the same rule.

Treating 'unknown' as 'no' is how a player gets debited for a round that was voided, so we never do it. The round stays open with a pending transaction until reconciliation retries the same key and finds out what happened. A round left open can be recovered. A round settled on a debit that never landed cannot.

None of that asks anything unusual of a wallet. It asks for one thing that most implementations get wrong under load: answer the same key with the same outcome, and keep the caller's errors apart from your own. A 4xx is a decision; a 5xx is weather. Our reference wallet implements all four endpoints correctly and has deliberate faults you can switch on, so you can watch a conformance suite catch them before you trust it.

Three tiers, and never a branch per operator

Every 'can casino X have it different?' gets one of three answers. None of them is a fork or a new release.

Tier

1

A licence config override

Bet levels, feature toggles, the opening stake, sound and, since 2026-08-29, the skin: anything the version already declares.

Certification: None. A config key cannot reach the maths.

licensing.license_config_overrides · One row. No release, no build, no re-certification.

Tier

2

A pinned build

They want the build they tested, or they are mid-audit, or they are not ready for the new one.

Certification: The pinned build keeps its own certificate; nothing is re-tested because nothing changed.

licensing.license_games.pinned_version_id · One column. Different operators run different builds concurrently at zero engineering cost.

Tier

3

A new variant

A genuine maths or feature fork. The game is a different game.

Certification: A full pack per rung, and a laboratory certificate per market before any real-money launch.

catalog.game_variants · Its own version, its own math profiles, its own market rules, its own price.

The market’s regulatory overlay is applied last, after every operator override, using clamps instead of a plain merge. A maximum stake can only come down and a minimum interval can only go up, so an operator cannot configure their way out of a constraint. What each market clamps

What happens when something goes wrong

01

The build checks itself

Every file that can change an outcome is hashed into a release manifest. The server re-hashes them at boot and every twenty-four hours and writes the result to an append-only log.

compliance.integrity_checks: append-only, and it cannot be edited or deleted.

02

A kill switch a person turns

Stops new play platform-wide, lets rounds in flight finish, and records who turned it and why.

ops.set_gaming_enabled(false, reason)

03

An interrupted round finishes

A round left unresolved is completed using the original idempotency key read back from the bet transaction, so an attempt that already credited replays instead of paying twice. A new bet is refused while a round is unresolved.

GLI-19 §4.16.3

04

Game recall

The player can see the most recent part of their own round history, with every event as it was recorded.

GLI-19 §4.14

05

Integrity, nightly

Stranded rounds, pending transactions and orphaned events are counted every night and any non-zero count raises an alert.

ops.check_gameplay_integrity()

06

A reference wallet with deliberate defects

We ship a correct implementation of our own wallet API, with faults you can switch on, so a test house can see the conformance suite catch a fault for itself.

npm run wallet:conformance

What is not built yet, and what we are not offering yet

Nothing is deployed, and no operator is connected

There is no hosting, no TLS and no production deployment. The wallet chain runs against our own reference implementation, not a live operator sandbox. The certified client (the pinned, hashed build an operator would embed) does not exist yet. The engine room is built; the deployment is not.

We are not asking anybody to integrate with us directly

Our titles reach players through an established remote gaming server, and that is the plan, not a stopgap. A studio with no track record cannot ask an operator to book an integration date, and the fastest route to a player is the server that operator already runs. The engine room runs behind our games on that server.

A direct integration is a later product. We will build it while the catalogue earns, prove it with the brands we are closest to, and offer it once it is worth an operator’s quarter to adopt.

Two conversations we want to have

Carry the titles, or ask for them

If you run a remote gaming server or an aggregator, this is what sits behind the games you would carry. It is built to one contract, so the tenth costs you what the first did. If you are an operator, tell us which server you run and we will come to you through it. Either way, know this first: nothing is deployed yet, and no laboratory has certified anything.

hello@getawaygaming.com · Operators and aggregators can also use the enquiry form on the contact page.