From 2017 to 2022 we worked, through a partner company, in the development team of win2day, the online casino and lottery platform of Austrian Lotteries, and one of our responsibilities was implementing third-party games via web services. Each game studio had a different interface, a different authentication method and different ideas about who is responsible for the game result and the movement on the player’s account. The first integrations took months and the last ones only weeks, and what made the difference was one internal contract. This article is for architects and development leads who connect an external service to a system where money moves.
Start with the wallet model: who holds the balance
In the seamless (single) wallet model the player’s balance stays in the operator’s system and the game server calls its interface for every bet and win: debit the stake, credit the win, roll back. In the transfer wallet model part of the funds is moved to an account with the game provider before play and back afterwards. Individual bets do not touch the operator’s system.
With a seamless wallet the operator’s interface sits on the critical path of every round: its availability and latency decide whether the game runs, and the number of calls grows with bets, not sessions. In return there is a single balance, every bet is visible immediately and limits can be applied to it. With a transfer wallet there are few calls, but part of the player’s money sits outside your own ledger, can be stuck if the provider goes down, and the operator learns about individual bets only from the provider’s reports. The player holds the account with the operator, so it is the operator who answers to the player for a correct balance and payout. The contract therefore has to state unambiguously which model is used and what happens to the funds when either side is down.
One internal interface, one adapter per provider
The platform has a single interface of its own for games: launch, bet, pay out, cancel, query state. Each studio has an adapter that translates its interface into ours. In the vocabulary of domain-driven design (Eric Evans) this is an anti-corruption layer: the foreign model, names and error codes stay behind the adapter boundary, and the platform core does not change for a new studio.
Format translation, error-code mapping and call verification belong in the adapter. Posting rules, limits and the transaction state machine do not: they live in the core, once for everyone. Once business logic leaks into adapters, a handful of providers later you have a handful of slightly different accounting rules. The layer also has a cost, which the Azure Architecture Center pattern catalogue lists for this pattern: another component to run and an extra hop that adds latency.
Verifying calls: signatures, game sessions, replays
Authentication differs between providers more than anything else: from HTTP Basic over TLS with an address allow-list to signatures over the message body. In our case every call was signed on both sides. With a shared secret the standard is HMAC as defined in RFC 2104. Home-grown constructions such as hash(secret + message) are vulnerable to length-extension attacks with hashes like SHA-1 or SHA-256. The signature is compared with a constant-time function and covers the whole body including a timestamp or nonce, otherwise a captured message can be sent again. HMAC only proves, however, that the message was created by a holder of the shared key, which means either party. Proving which party sent it requires an asymmetric signature, which our article on online payment security covers for payment requests.
The second layer is the game session token. The platform issues it when the game is launched. The token is bound to the player, the game and the currency and has a limited lifetime, and the platform identifies the player by the token, not by an identifier in the message body. A win or a rollback for an already accepted bet can arrive after the session has expired, though, so the rule “invalid token, reject” is sensible for new bets, not automatically for every call.
Idempotency, timeouts and rollbacks
HTTP on its own does not make money calls idempotent: under RFC 9110 the idempotent methods are PUT, DELETE and the safe methods, not POST. Game providers nonetheless retry routinely on timeouts or unreadable responses, often several times with increasing back-off. Every money call therefore carries a unique transaction identifier, which the platform records under a uniqueness constraint in the database itself. A repeated delivery with the same identifier and content returns the original response without posting again; the same identifier with different content is an error, not a retry.
The timeout is the treacherous case. The game server does not know whether the platform posted the bet, so it sends a rollback, which may arrive before the bet itself or for a transaction the platform never saw. It has proved worthwhile to record a rollback even for an unknown transaction, so that a bet arriving later with the same identifier is refused. The adapter contract must cover where providers differ: the response to a rollback of an unknown transaction, whether a rollback returns the amount of the original transaction or the amount in the request, whether rolling back a settled transaction is allowed, and what happens once retries are exhausted.
- Every transaction has a state: received, verified, posted, cancelled. Only the defined transitions are allowed. An invalid transition is rejected and recorded, not quietly “fixed”.
- A posted transaction is never deleted or overwritten. A rollback after posting is a new, opposite entry that references the original transaction.
- A dropped connection mid-game is a normal state, not an exception. The platform can finish or revert every open transaction and regularly looks for those that have stayed in an intermediate state unusually long.
- Every transition records the time, the originator (player, game server, operator, reconciliation) and the request identifier, so that the course of a round can be reconstructed years later.
Game results, amounts and currencies
The game result is produced on the provider’s server and the platform does not recompute it, because the random number generator and the game maths belong to the provider. It does not accept the result unchecked, though. Before posting, it verifies what it can: that the win belongs to an existing, non-cancelled bet of the same player and round, that the currency matches the session, that the amount does not exceed the maximum win the game rules allow for that game and stake, and that the round has not already been closed. A result outside these bounds is not posted automatically but held for review.
Amounts are not transferred as floating-point numbers, because the value 0.1 cannot be represented exactly in binary. Safe options are integers in the currency’s minor unit or a fixed-precision decimal type, with the ISO 4217 currency stated explicitly in every call. Not every currency has two decimal places: under ISO 4217 the Japanese yen has none and the Kuwaiti and Bahraini dinars have three. If the provider calculates a win as a multiple of the stake, rounding (who rounds, in which direction, to how many places) has to be agreed and covered by a test, otherwise it shows up in reconciliation as a stream of small discrepancies.
Daily reconciliation and what to do about a discrepancy
Reconciliation with the provider runs daily over a closed period and compares individual transactions, not just totals, because a total can match even when two errors cancel each other out. Discrepancies come in four types: the transaction is missing on our side, missing on the provider’s side, the amount differs, or the state differs. The report format, the cut-off time zone and which day a round belongs to when it crosses midnight need agreeing too.
The procedure for a discrepancy matters more than the comparison itself. A discrepancy stops the affected payouts until it is explained. It is not resolved by editing a balance directly in the database but by finding the cause in the transaction trail on both sides. A correction goes in as a new, audited entry, ideally approved by a second person. A recurring type of discrepancy is a defect in the contract or the adapter and belongs in the test vectors.
Test vectors and certification
Without tests a contract is just a document, so it comes with test vectors: for signatures, known inputs and expected outputs byte for byte; for money flows, scenarios such as a repeated bet, a timeout followed by a rollback, a rollback before the bet, a win without a bet, a win after a rollback, or a currency with a different number of decimal places. A contract, test vectors and a reference adapter implementation meant that launching a new game was a repeatable procedure with a checklist, not a project.
Certification is a separate layer. In regulated markets it is common for games and random number generators to be tested before release by an independent test house against the regulator’s technical standards or widely used ones such as GLI-19 for interactive gaming systems. The UK Gambling Commission’s testing strategy, for instance, requires testing by an approved test house before a new game is released, retesting when changes affect fairness, and testing when an existing game is made available through a new channel. For the integration this means the platform should record which game version the player played, and a change on our side that alters what the player sees may not be “purely technical”. The applicable requirements are set by the regulator of the market concerned.
Checklist for a new game provider
- Wallet model: seamless or transfer, and what happens to the funds when either side is down.
- Signing and sessions: the algorithm, exactly what is signed and in which encoding, replay protection, zero-downtime key rotation, session token lifetime and how payouts behave after it expires.
- Identifiers: unique transaction and round identifiers, the scope of their uniqueness and the response to a duplicate.
- Timeouts and retries: how long the provider waits, how often and at what interval it retries, when it sends a rollback and what happens once retries are exhausted.
- Rollbacks: behaviour for unknown, already rolled-back and already settled transactions, and which amount applies.
- Amounts and currencies: amount format, supported currencies, precision and rounding rules.
- Error codes: the complete list, the distinction between temporary and permanent errors, and the mapping to internal states.
- Reconciliation: report format and frequency, cut-off time zone, contact and response times for discrepancies.
- Certification: game certificates and versions for the target market, and how the provider announces changes that require retesting.
- Testing and operations: a test environment in which failure scenarios can be triggered, incident contacts, planned maintenance and the ability to switch off one game without affecting the others.
When it pays off, and what we recommend
Your own adapter layer with a contract pays off when the number of providers grows and integrations repeat. With one or two providers, or when the platform connects through an aggregator that already unifies the interfaces, it may be unnecessary. The aggregator’s interface then becomes the contract, and the same questions about the wallet, rollbacks and reconciliation have to be put to the aggregator. We recommend writing the contract and test vectors first and the adapter second. In a regulated environment, where an error in moving money can mean complaints, audits and explanations to the regulator, routine is the most valuable thing you can build.