Skip to content

Server-side integration

@moose/provider-sdk's ProviderClient is your entry point for everything your backend needs: session verification, balance queries, and wallet transaction submission. Signing, nonces, and idempotent retry are all handled for you.

Install

Distributed directly by moose-platform as an npm package — your integration contact will provide access.

Create a client

ts
import { ProviderClient } from '@moose/provider-sdk'

const client = new ProviderClient({
  baseUrl: 'https://platform.example.com',
  tenantId: 'acme-studio',
  secret: process.env.PLATFORM_SECRET!,
  // retry: { maxAttempts: 5, baseDelayMs: 200 }, // defaults shown
})

secret is your tenant's HMAC signing key — see Security below.

A typical integration calls these in order: verifySession once when the game loads, getBalance wherever you need to display or resync a balance, and submitTransaction for every bet/win/rollback in a round.

Verify a session

When your game starts, validate the launch token and read the resolved Central Config (RTP label + bet limits) for this (game, operator) pair. sessionToken here is the value your browser client read from the launch URL and sent to your backend — see Getting the session token for how it gets there:

ts
const session = await client.verifySession(sessionToken)
// {
//   playerRef, gameId, operatorId, currency,
//   config: { rtpProfile, minBetMinor, maxBetMinor } // maxBetMinor === 0 means no upper limit
// }

With session in hand, you're ready to query a balance or submit transactions against it.

Query a balance

ts
const { balance } = await client.getBalance(sessionToken)
// balance is minor units (cents), same shape as a transaction's `balance`

Only the session token is required — playerRef is resolved from the session server-side, so this can never be used to read another provider's player's balance. It moves no money: the operator's wallet stays the source of truth, and this is a pass-through query.

Every submitTransaction response already returns the post-transaction balance, so you generally don't need this mid-round — reach for it before the player's first bet, or to resync after a reconnect. A DEMO session returns its in-memory fun-play balance instead of querying a real operator. Unlike submitTransaction, this call is not idempotent-tracked on the platform side — it's a read, so a failed attempt can simply be retried. See Errors & retry for the full status code mapping.

Submit a transaction

ts
const bet = await client.submitTransaction({
  transactionId: crypto.randomUUID(),
  sessionToken,
  type: 'BET',            // 'BET' | 'WIN' | 'ROLLBACK'
  roundId,
  roundComplete: false,    // true on the round's last transaction (BET/WIN only)
  playerRef: session.playerRef,
  amount: 300,             // minor units (cents) — never floats
  currency: session.currency,
  gameId: session.gameId,
})

if (bet.status === 'DECLINED') {
  // A normal business outcome (insufficient funds, or outside the
  // configured bet limits) — not an error, never retried.
}
  • transactionId — generate it yourself (e.g. crypto.randomUUID()). Reuse it verbatim across your own retries of the same logical attempt; the SDK's internal retries already reuse it automatically.
  • ROLLBACK — set originalTransactionId to the BET's transactionId being reversed.
  • WIN — submit as a separate transaction from its BET, marking roundComplete: true on whichever of the two ends the round.

What's retried, and what isn't

OutcomeBehavior
Network error (DNS, connection refused, etc.)Retried with backoff
HTTP 409 (another attempt for this transactionId is in flight)Retried with backoff
HTTP 5xx (includes a TIMED_OUT resolution on the platform's side)Retried with backoff
HTTP 400/401/403/404Thrown immediately as PlatformApiError, never retried — the request itself is invalid or unauthenticated, so retrying won't help
HTTP 429 (rate limit exceeded)Thrown immediately as PlatformApiError, not retried by the SDK. PlatformApiError.retryAfter carries the Retry-After header when the platform sends one — honor it before your own retry, falling back to a fixed delay if it's absent
200 response with status: "DECLINED"Returned normally, not an error, never retried — a BET's business outcome

Retries always reuse the exact same transactionId, matching the platform's idempotency contract — you never need to implement this yourself. See Errors & retry for the full status code reference.

Inbound webhooks

Three things the platform calls you for — session revoke and the two free-spins routes — live outside this SDK entirely, since your server is the one receiving the call, not sending it. See Platform → provider webhooks for the shared contract, and Session revoke webhook / Free spins webhook for each route's full details.

The session itself is already gone on the platform's side by the time the revoke call arrives — it's a best-effort "do it now" notification, not the source of truth. Your own next verifySession/submitTransaction call for that sessionToken already fails with 401 regardless of whether this webhook was ever delivered. For a browser-facing game, report the revoke up to the shell via @moose/game-client-sdk's notifySessionRevoked — see the game-client-sdk reference.

Round replay

An operator can request a link to replay one of your game's rounds. The platform points that link at your replay page — this only works once you've configured a replay page base URL for your game with the platform team and recorded a replay log for the round at spin time (the platform only ever sees money movements, never your game's visual outcome, so there's nothing for it to replay from if you haven't). See Round replay for the full contract, verifyReplay, and a worked example.

Signing

Every request is signed with HMAC-SHA256 over a canonical string built from the method, path, timestamp, nonce, and body, sent as four headers: X-Tenant-ID, X-Timestamp, X-Nonce, X-Signature. ProviderClient generates and sends these automatically — you only need the lower-level signRequest/generateNonce exports if you're implementing a provider-side call the SDK doesn't cover, or verifyPlatformSignature if you're implementing one of the inbound webhooks above. See Signing & authentication for the full contract, including clock skew, nonce reuse, and secret rotation.

Security

secret is your tenant's HMAC signing key. Keep it server-side only — never ship it to a browser. This SDK is for your backend (RGS), not the player-facing game client — see Getting the session token and Reporting lifecycle events for that half of the integration.

Debugging

An unexplained 401, a silently-retrying call, or wanting to run your integration tests without the full platform stack up — see the Debugging guide for onDebug, explainSignature, and PlatformApiError's hint/requestId, and Testing with createMockPlatform for the offline createMockPlatform.

Full reference

See @moose/provider-sdk reference for every exported type and method signature.