Phoenix Gamification

Events and Integration

This guide explains how to send gameplay or engagement events into Phoenix so they can be scored against active leaderboards. It covers the ingestion pipeline, payload schema, authentication, retries, and best practices for keeping data consistent at scale.

1. Pipeline Overview

  1. Client or game backend signs an HTTP request and POSTs events to the Ingestion Gateway.
  2. Ingestion Gateway validates the request (HMAC + JSON schema), persists it to Phoenix’s event log, and acknowledges once the event is durable.
  3. Leaderboard Engine consumes the log, evaluates events against each active leaderboard for the tenant, and updates the live standings store plus longer-term history.
  4. Query & WebSocket Gateways expose the resulting standings to your applications.

Because the event log deduplicates on event_id, you can retry failed HTTP requests safely—duplicates within the configured window (default 300 s) are ignored.

2. Endpoint Summary

MethodPathDescriptionAuthBody LimitNotes
POST/v1/eventsIngest a single eventTenant HMAC headers1 MBevent_id must be unique per tenant
POST/v1/events/bulkIngest up to 100 events in one callTenant HMAC headers1 MBFails fast if list is empty or >100
GET/health, /readyHealth/readiness statusNoneUseful for deployment checks

All ingestion endpoints are scoped per tenant. The gateway overrides any tenant_id field in the payload with the ID derived from your HMAC headers, preventing cross-tenant injection.

3. Authentication & Signing

Ingestion uses Phoenix’s Tenant HMAC scheme:

  1. Build the canonical string: METHOD\nPATH\nTIMESTAMP\nBODY.
    • METHOD is the uppercase HTTP verb (POST).
    • PATH is the route only (e.g., /v1/events), no host or query string.
    • TIMESTAMP is Unix time in seconds (UTC) and must be within ±300 s of server time.
    • BODY is the exact JSON you send (use "" for GET requests).
  2. Sign the canonical string with your tenant secret using HMAC-SHA256; prefix the hex digest with hmac-sha256=.
  3. Send the following headers:
X-Tenant-Id: <TENANT_ID>
X-Timestamp: <timestamp>
X-Signature: hmac-sha256=<hex>

Important: The gateway enforces a 1 MB body limit and rejects malformed UTF-8 before verifying the signature.

Node.js signing snippet

import crypto from "crypto";

function buildSignature(
  secret: string,
  method: string,
  path: string,
  body: string
) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const canonical = [method.toUpperCase(), path, timestamp, body].join("\n");
  const digest = crypto
    .createHmac("sha256", secret)
    .update(canonical)
    .digest("hex");
  return { timestamp, signature: `hmac-sha256=${digest}` };
}

Reuse the same helper for both single and bulk endpoints (and later for tenant-level Admin/Query calls if you choose HMAC instead of API keys).

4. Event Schema (IngestionEventV1)

Phoenix provides a stable event contract for all ingestion calls. Required fields:

FieldTypeDescription
tenant_idstringAutomatically set by the gateway; include any placeholder value on your side.
event_idstringGlobally unique ID per event (UUID recommended). Used for deduplication and idempotency.
typestringLogical event type, e.g., match.completed, purchase.settled, xp.granted.
actor.user_idstringThe player/account receiving points on leaderboards.
subjectobject (optional){ "type": "quest", "id": "quest-77", ... } describing the entity involved.
occurred_atstring (ISO 8601)When the event actually happened; cannot be more than 1 hour in the future.
attrsobjectArbitrary key/value bag used by scopes, conditions, and scoring expressions.

Sample payload:

{
  "tenant_id": "placeholder",
  "event_id": "evt_01JBQ56ZGTKNC3XN8R8KZZR4N5",
  "type": "match.completed",
  "actor": {
    "user_id": "user-123",
    "metadata": {
      "region": "na-east",
      "platform": "ios"
    }
  },
  "subject": {
    "type": "match",
    "id": "match-987"
  },
  "occurred_at": "2025-11-18T12:34:56Z",
  "attrs": {
    "score": 1550,
    "duration_seconds": 480,
    "victory": true
  }
}

Best practices:

  • Event IDs: Use ULIDs/UUIDs to guarantee uniqueness; duplicates detected within the dedupe window are dropped automatically.
  • Occurred at: Send the real event time so Phoenix can place scores into the correct hourly/daily windows.
  • Attributes: Keep attribute names short but descriptive (score, kills, coins_spent)—they are referenced in leaderboard scoring expressions (attrs.score * 0.1, attrs.victory etc.).

5. How Events Drive Leaderboards

Behind the scenes Phoenix applies every event to the leaderboards you configure:

  1. Each leaderboard declares scoring rules with:
    • event_type (exact match or * wildcard).
    • Optional conditions using attribute paths (e.g., {"attrs.mode": {"eq": "ranked"}}).
    • points_expression that can reference attrs.*, do arithmetic, and call min, max, abs.
  2. When an ingested event matches a rule, the resulting points are applied to the user for the relevant time window.
  3. Aggregation (sum, count, max, min, avg) determines how multiple events combine within a window.
  4. Scope filters (configured per leaderboard) can inspect any field on the event JSON before scoring.

Implication: Make sure every attribute your scoring/filters reference is present in attrs or the standard fields above—otherwise events will be ignored silently (no rule match).

6. Bulk Ingestion

POST /v1/events/bulk accepts:

{
  "events": [ { ...IngestionEventV1... }, ... ]
}

Constraints enforced by the gateway:

  • Minimum 1 event, maximum 100 events per request.
  • Total body (after JSON encoding) must be ≤ 1 MB.
  • Each event is validated independently; the response returns per-event status:
{
  "status": "partial",
  "total": 3,
  "accepted": 2,
  "failed": 1,
  "results": [
    { "event_id": "evt_1", "status": "accepted" },
    {
      "event_id": "evt_2",
      "status": "failed",
      "error": "Validation failed: event_type cannot be empty"
    },
    { "event_id": "evt_3", "status": "accepted" }
  ]
}

Retry only the failed events; re-sending accepted ones with the same event_id will be deduplicated but wastes bandwidth.

7. Retry, Ordering, and Idempotency

  • HTTP retries: Safe as long as you reuse the same event_id. The event log ignores duplicates within the configured 300 s dedupe window.
  • Ordering: Events are processed roughly in publish order per tenant, but you should not rely on strict ordering; leaderboard windows are keyed by occurred_at.
  • Clock skew: If your systems can drift more than ±5 minutes, sync with NTP or apply time skew correction before signing requests; otherwise HMAC validation fails.
  • Idempotent consumer: The leaderboard service also double-checks window/timestamps, so late events may still land in the correct historical window as long as it is still open.

8. Error Handling

HTTP StatusCodeMeaning / Resolution
400VALIDATION_ERRORMalformed JSON, missing fields, batch size >100, occurred_at too far in future, etc. Fix payload.
401UNAUTHORIZEDHMAC headers missing/incorrect. Recalculate signature, ensure timestamp is UTC seconds.
403FORBIDDENTenant is inactive/suspended. Contact Phoenix support.
500INTERNAL_ERRORTransient ingestion/storage issue. Wait and retry.

Responses always follow:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Bulk request can contain maximum 100 events",
    "status": 400
  }
}

Log both the HTTP status and error.code in your observability stack for faster debugging.

9. Monitoring & Observability Hooks

  • GET /health – basic liveness (service started).
  • GET /ready – confirms the gateway can persist incoming events; returns 503 if downstream persistence is unavailable.
  • Emit logs/metrics on your side for each event_id sent plus the HTTP status; this simplifies reconciliation with Phoenix support.

10. Checklist Before Moving On

  • Generated HMAC signatures successfully in your language of choice.
  • Ingested at least one single event (202 Accepted).
  • Ingested a bulk batch and handled per-event failures.
  • Verified the leaderboard you care about updates after sending events.

Once you have dependable ingestion, continue to Leaderboards Overview to understand how scopes, timeframes, scoring, and ranking settings interact with the events you just wired up.