Phoenix Gamification
Leaderboards

Real-time Updates

Phoenix offers a managed WebSocket channel for live leaderboard streams. Use it when you need immediate score changes in your game client, spectator dashboard, or broadcast overlays.

1. Endpoint & Authentication

GET /v1/leaderboards/{tenant_id}/{leaderboard_id}/ws?timestamp=<unix>&signature=<hmac>
  • Protocol: Secure WebSocket (wss://).
  • Headers: Standard WebSocket headers plus any required by your environment.
  • Query params:
    • timestamp – Unix time in seconds (UTC), must be within ±5 minutes of Phoenix servers.
    • signaturehmac-sha256=<hex> generated with the tenant’s secret.

Canonical string for signing:

WS
/v1/leaderboards/{tenant_id}/{leaderboard_id}/ws
{timestamp}

This mirrors the tenant HMAC scheme described in getting-started.md. Phoenix validates the signature and ensures the tenant + leaderboard are active before upgrading the connection.

2. Connection Workflow

  1. Resolve leaderboard – ensure the leaderboard is active and you have the tenant’s secret.
  2. Generate signature – build the canonical string above, sign with the tenant secret, and append signature and timestamp query params.
  3. Open WebSocket – use your preferred WebSocket client. On success, Phoenix immediately sends a snapshot for page 1 (size 100 by default).
  4. Send configuration – optionally send a configure message to set custom pagination, filters, or window keys.
  5. Stream updates – Phoenix delivers JSON messages as scores change, keeping your UI synchronized.

Connections idle for more than 30 seconds are pinged automatically; respond with pong frames or simply rely on Phoenix’s built-in heartbeat messages.

3. Client Messages

To modify what you receive, send JSON messages with the following structure:

{
  "type": "configure",
  "page": 2,
  "page_size": 50,
  "filter": { "type": "top_n", "n": 100 }
}
Message TypeFieldsPurpose
configurepage (optional), page_size (optional), filter (optional), window_key (optional)Change the active page, size, filter strategy, or request a different time window.
request_pagepage (required), page_size (optional)Fetch a specific page immediately without changing saved preferences.

Filters (filter.type) mirror the options exposed by Phoenix’s WebSocket handler:

  • none – receive all updates (default).
  • page_range – only entries affecting a specific page.
  • page_aware – current page plus users who might enter it (supports buffer_size).
  • top_n – top N users regardless of pagination.
  • user_ids – “watchlist” of specific users.
  • rank_range – updates for a rank band (e.g., 1–50).

Phoenix acknowledges configuration changes by returning a config_ack message that echoes the active settings.

4. Server → Client Messages

All outbound messages are JSON with the type field shown below:

TypeWhen it’s sentPayload Highlights
snapshotImmediately after connecting or when changing windows/filters.Full page of entries, total counts, pagination metadata.
page_dataResponse to request_page.Same shape as snapshot.
score_updateWhenever a tracked user’s score changes and passes your filter.user_id, new_score, and optionally new_rank.
config_ackAfter Phoenix applies a configure request.Confirms page, size, filter currently in effect.
errorInvalid client request (malformed JSON, unknown filter type, etc.).message describing the issue; connection stays open.
pingHeartbeat every ~30 seconds.Timestamp for monitoring round-trip latency.

All message schemas are documented in the services/query-gateway/src/handlers/websocket.rs file and remain stable for client integrations.

5. Filtering Strategies & Pagination Tips

  • Top N overlays – use filter: { "type": "top_n", "n": 50 } to power broadcast tickers without paginated requests.
  • Page-aware UIspage_aware ensures you see anyone who might enter the current page (configurable buffer_size defaults to page_size).
  • Watchlists – track specific users or squads by sending user_ids. Phoenix only delivers updates for those IDs, reducing noise.
  • Rank range highlights – perfect for spotlighting mid-tier brackets (“ranks 500–550”).

To keep clients in sync:

  1. Open WebSocket and display the initial snapshot.
  2. Listen for score_update messages and patch your UI.
  3. Periodically refresh via request_page to handle large jumps (e.g., a user moving multiple pages at once).
  4. Handle config_ack to confirm server-side pagination state matches your UI.

6. Handling Windows

By default, Phoenix streams the current window for the leaderboard’s timeframe. If you need a completed window:

  • Include window_key in your configure message (e.g., "window_key": "2025-11-18T12" for hourly).
  • If the requested window is historical, Phoenix sources data from its snapshot store and still emits updates (none for completed windows, but the page data is returned).
  • If the window doesn’t exist, you receive an error message with details.

7. Error & Disconnect Scenarios

ConditionServer BehaviorRecommended Client Action
Invalid signature/timestampConnection rejected with HTTP 401 before WebSocket upgrade.Recalculate HMAC, ensure timestamp accuracy.
Tenant or leaderboard inactiveConnection rejected with HTTP 403/404.Surface UI error; prompt operators to activate the leaderboard.
Malformed client messageConnection stays open; Phoenix sends error message.Fix payload and resend.
Idle timeout / heartbeat failurePhoenix closes the connection after repeated missed pings/pongs.Reconnect automatically.
Internal server issueConnection may drop; Phoenix logs the incident.Apply exponential backoff before reconnect attempts.

Always treat WebSocket connections as transient. Implement reconnection logic and resume from the latest known page/window.

8. Sample Client (TypeScript)

import WebSocket from "ws";

const tenantId = "tenant-prod-001";
const leaderboardId = "xp-weekly-global";
const { timestamp, signature } = buildSignature(
  TENANT_SECRET,
  "WS",
  `/v1/leaderboards/${tenantId}/${leaderboardId}/ws`,
  "" + Math.floor(Date.now() / 1000)
);

const ws = new WebSocket(
  `${QUERY_WS_URL}/v1/leaderboards/${tenantId}/${leaderboardId}/ws?timestamp=${timestamp}&signature=${encodeURIComponent(
    signature
  )}`
);

ws.on("open", () => {
  console.log("Connected");
  ws.send(
    JSON.stringify({
      type: "configure",
      page: 1,
      page_size: 50,
      filter: { type: "page_aware", page: 1, page_size: 50, buffer_size: 50 },
    })
  );
});

ws.on("message", (data) => {
  const msg = JSON.parse(data.toString());
  switch (msg.type) {
    case "snapshot":
    case "page_data":
      renderLeaderboard(msg.entries);
      break;
    case "score_update":
      applyScoreUpdate(msg);
      break;
    case "ping":
      ws.pong();
      break;
    case "error":
      console.error("Server error:", msg.message);
      break;
  }
});

Implement your own buildSignature using the canonical string described earlier.

9. Checklist

  • Generate tenant HMAC signatures for WebSocket URLs.
  • Handle snapshot, page_data, score_update, and ping messages.
  • Implement reconnection logic with exponential backoff.
  • Use filters to limit bandwidth (top N, page-aware, watchlists).
  • Surface errors to operators when Phoenix rejects a connection.

Next guide: Querying Results , covering REST APIs for live pages, user-centric queries, and historical snapshots.