Skip to main content

Tenancy

One SlotBattle instance serves several casinos at once. A tenant is one casino: its own battles, its own game allowlist, its own credentials, its own webhook endpoint. You are one tenant on the platform's instance.

What a tenant owns, and what the platform owns

This split defines multi-tenancy in SlotBattle. Check it before promising players something the instance cannot provide.

Belongs to the tenantBelongs to the platform (the instance)
Battles and seatsThe viewer-token signing key
Game allowlistThe three lobby windows
SoftSwiss backend credentialsMail configuration
Result webhook endpoint and secretThe recorder and its callback secret
API keys and their scopesStorage, CDN, database, Redis
Console users and their roles

You can change everything in the left column and nothing in the right one. A different lobby window is a request to the platform, not a setting you own.

See What the host controls.

How a tenant is identified on a request

Two surfaces, two mechanisms, no overlap.

The S2S API derives the tenant from the API key. There is no tenant header and no tenant field to send: responses and webhooks echo the id, and nothing accepts it as input.

The tenant never comes from the request body

A tenant id read off a payload turns every resource route into a cross-tenant read for anyone willing to edit a JSON field. Deriving it from the credential makes that class of bug unrepresentable.

The admin console carries the active tenant in an X-Tenant-Id header, alongside the session cookie. It is a header rather than a claim in the token so that an operator with access to several casinos can switch between them without re-authenticating. Membership is checked on every request, so the header states an intent rather than an entitlement.

Cross-tenant reads answer 404

Asking for a battle that belongs to another casino returns 404 Not Found, the same answer as asking for a battle that does not exist.

A 403 would confirm the battle exists, turning a permission error into an enumeration oracle: probe ids, and the ones answering 403 are real. Every battle-scoped route loads the battle and checks ownership before it mutates anything, so a cross-tenant write cannot take effect and then be rejected.

A suspended tenant is the exception and does get a 403. The caller is legitimate, so naming the reason is correct.

Where a tenant's credentials live

Two places, resolved in a fixed order.

Settings documents, per tenant, editable from the console:

CategoryScopeHolds
softswissTenantThat casino's backend credentials
webhookTenantThat casino's result endpoint and signing secret
lobbyPlatformThe three lobby windows

The platform's own credentials are the fallback, and are not yours to change. On an instance where every casino brings their own, the fallback is left empty, which is why a missing softswiss document surfaces as bot sessions minted under the wrong casino id rather than as an error.

Three resolution rules that prevent credential leaks

A complete document wins; an empty one falls back to the environment.

A credential group is all-or-nothing. Filling in your own backend URL but leaving the auth token blank is refused at the write, while an operator is still present to fix it. Half filled, it would send the platform's credential to your endpoint.

A resolution failure is an error, never a fallback. If the settings read fails, or a stored secret cannot be unsealed, the operation fails. Falling back would let a transient database fault present the platform's credential under a casino's identity.

Secrets never come back

Reading a settings document answers "<field>Set": true|false instead of the value. A submitted body that omits a secret leaves the stored one alone, because the client could not have received it. Treating an absent field as "clear this" would let pressing Save wipe a working credential.

Saved settings are cached briefly and invalidated explicitly on write. With only a TTL, a console save would stay invisible until the entry lapsed: the database correct, the running system still on the old credential, and nothing reporting an error.

The instance-level allowlist

Separately from the tenants table, SLOTBATTLE_TENANTS lists which tenant ids a process will serve at all. It carries no credential and performs no authorisation; it is a membership list. A tenant absent from it is unknown to that process even if its database row exists.

Game allowlists

Each tenant has an allowlist of games, and the catalog endpoint returns only the allowed ones.

An empty allowlist means an empty catalog

It does not mean "all games". A newly created tenant with nothing allowed sees zero games and every battle creation fails, which reads as a broken integration. Allowing games is part of onboarding.

The console's game screen answers the whole catalog with a boolean per game, because a screen limited to allowed games could not be used to allow a new one.

Next