Skip to content

Authentication

Slotty Labs uses API keys and SSO tokens to authenticate operators and players.

API Key Format

EnvironmentPrefixExample
Sandboxsk_sandbox_sk_sandbox_abc123def456...
Productionsk_production_sk_production_xyz789ghi012...

DANGER

Sandbox and production keys are NOT interchangeable. A sandbox key will be rejected in production, and vice versa.

Integration Modes

Slotty Labs supports two integration modes:

Transfer Mode

The platform manages player authentication entirely. Slotty Labs creates and manages player accounts internally.

  • Simpler to integrate
  • Less control over player lifecycle
  • Suitable for smaller operators

The operator manages player authentication via SSO. The operator's backend creates launch tokens that map to existing player accounts.

  • Full control over player identity
  • Single sign-on experience
  • Recommended for B2B integrations

SSO Token Flow

The SSO flow consists of 5 steps:

┌──────────┐     ┌──────────────┐     ┌──────────────┐
│ Operator │     │  Slotty Labs │     │  Game Client  │
│ Backend  │     │     API      │     │   (iframe)    │
└────┬─────┘     └──────┬───────┘     └──────┬────────┘
     │                  │                    │
     │ 1. POST /auth/sso/create-token       │
     │  (API key + HMAC)│                    │
     │─────────────────>│                    │
     │                  │                    │
     │ 2. JWT launch    │                    │
     │    token (30s)   │                    │
     │<─────────────────│                    │
     │                  │                    │
     │ 3. Token in URL  │                    │
     │──────────────────┼───────────────────>│
     │                  │                    │
     │                  │ 4. POST /auth/sso/exchange
     │                  │<───────────────────│
     │                  │                    │
     │                  │ 5. Session token   │
     │                  │───────────────────>│
     │                  │                    │
  1. Operator backend calls POST /api/v1/auth/sso/create-token with API key and HMAC signature
  2. Slotty Labs API returns a single-use JWT launch token with a 30-second TTL
  3. Operator passes the token to the game iframe via URL parameter
  4. Game client exchanges the token for a session via POST /api/v1/auth/sso/exchange
  5. Subsequent API calls use the session token from the exchange response

Tenant identity

Your tenant (operator) identity is derived from your API key on the server. You never send a tenant ID header — presenting a valid sk_... key is enough.

HMAC Request Signing

Sensitive API requests (SSO token creation) are additionally signed with your webhook secret (whsec_...). The official SDK does this automatically. Once a webhook secret is configured for your tenant, the signature becomes mandatory:

X-Slotty-Timestamp: <unix seconds>
X-Slotty-Signature: sha256=<HMAC-SHA256 of "timestamp.METHOD.path.body">

Signing Algorithm

typescript
import crypto from 'crypto';

function signRequest(
  secret: string,
  timestamp: number,
  method: string,
  path: string,
  body: string,
): string {
  const payload = `${timestamp}.${method}.${path}.${body}`;
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  return `sha256=${hmac.digest('hex')}`;
}

Timestamp Tolerance

The server accepts timestamps within ±5 minutes (300 seconds) of the current time. All comparisons use timing-safe equality to prevent timing attacks.

SSO Token Claims

The JWT launch token contains these claims:

ClaimDescriptionExample
subPlayer external ID"player-123"
tidTenant (operator) ID"tenant-abc"
curCurrency code"USD"
jurJurisdiction code"CW"
locLocale"en"
pidInternal player ID"pl_abc123"
gidTarget game ID (optional)"slotty-slots"
demoDemo mode flagfalse
unameDisplay username"Player123"
jtiUnique token ID"tok_xyz789"

Single-Use Enforcement

Each SSO token can only be used once. After exchange, the token's jti is stored in Redis:

Key:   {tenantId}:sso:used:{jti}
TTL:   120 seconds

Any attempt to reuse an exchanged token will return a SSO_TOKEN_ALREADY_USED error.

SSO Exchange Response

A successful token exchange returns:

typescript
interface SSOExchangeResponse {
  accessToken: string;       // JWT for API calls (15min TTL)
  refreshToken: string;      // Refresh token (7d TTL)
  sessionId: string;         // Unique session identifier
  player: {
    id: string;              // Internal player ID
    externalId: string;      // Operator's player ID
    username: string;        // Display name
    currency: string;        // Player currency
    jurisdiction: string;    // Jurisdiction code
  };
}

Session Information

Active sessions expose the following data:

typescript
interface SessionInfo {
  sessionId: string;
  playerId: string;
  tenantId: string;
  jurisdiction: string;
  activeGameId: string | null;
  sessionDuration: number;          // seconds elapsed
  maxSessionMinutes: number;        // jurisdiction limit
  nextRealityCheckAt: string;       // ISO 8601 timestamp
}

REST API Example

Create SSO Token

typescript
// POST /api/v1/auth/sso/create-token
const body = JSON.stringify({
  playerId: 'player-123',     // YOUR player identifier
  currency: 'USD',
  jurisdiction: 'CW',
  locale: 'en',
  gameId: 'slotty-slots',     // optional — pin the token to one game
  demo: false,                // optional — demo mode
});

const timestamp = Math.floor(Date.now() / 1000);
const signature = signRequest(
  webhookSecret, timestamp, 'POST', '/api/v1/auth/sso/create-token', body,
);

const response = await fetch('https://api.slottylabs.com/api/v1/auth/sso/create-token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer sk_production_abc123...',
    'X-Slotty-Timestamp': timestamp.toString(),
    'X-Slotty-Signature': signature,
  },
  body,
});

const { data } = await response.json();
// data.launchToken — single-use JWT, 30s TTL

Use the SDK

@slottylabs/sdk performs the API-key auth, HMAC signing, and error handling for you — the raw REST example above is only needed for non-Node.js backends.