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
- Client or game backend signs an HTTP request and POSTs events to the Ingestion Gateway.
- Ingestion Gateway validates the request (HMAC + JSON schema), persists it to Phoenix’s event log, and acknowledges once the event is durable.
- Leaderboard Engine consumes the log, evaluates events against each active leaderboard for the tenant, and updates the live standings store plus longer-term history.
- 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
| Method | Path | Description | Auth | Body Limit | Notes |
|---|---|---|---|---|---|
| POST | /v1/events | Ingest a single event | Tenant HMAC headers | 1 MB | event_id must be unique per tenant |
| POST | /v1/events/bulk | Ingest up to 100 events in one call | Tenant HMAC headers | 1 MB | Fails fast if list is empty or >100 |
| GET | /health, /ready | Health/readiness status | None | — | Useful 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:
- Build the canonical string:
METHOD\nPATH\nTIMESTAMP\nBODY.METHODis the uppercase HTTP verb (POST).PATHis the route only (e.g.,/v1/events), no host or query string.TIMESTAMPis Unix time in seconds (UTC) and must be within ±300 s of server time.BODYis the exact JSON you send (use""for GET requests).
- Sign the canonical string with your tenant secret using HMAC-SHA256; prefix the hex digest with
hmac-sha256=. - 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:
| Field | Type | Description |
|---|---|---|
tenant_id | string | Automatically set by the gateway; include any placeholder value on your side. |
event_id | string | Globally unique ID per event (UUID recommended). Used for deduplication and idempotency. |
type | string | Logical event type, e.g., match.completed, purchase.settled, xp.granted. |
actor.user_id | string | The player/account receiving points on leaderboards. |
subject | object (optional) | { "type": "quest", "id": "quest-77", ... } describing the entity involved. |
occurred_at | string (ISO 8601) | When the event actually happened; cannot be more than 1 hour in the future. |
attrs | object | Arbitrary 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.victoryetc.).
5. How Events Drive Leaderboards
Behind the scenes Phoenix applies every event to the leaderboards you configure:
- Each leaderboard declares scoring rules with:
event_type(exact match or*wildcard).- Optional
conditionsusing attribute paths (e.g.,{"attrs.mode": {"eq": "ranked"}}). points_expressionthat can referenceattrs.*, do arithmetic, and callmin,max,abs.
- When an ingested event matches a rule, the resulting points are applied to the user for the relevant time window.
- Aggregation (
sum,count,max,min,avg) determines how multiple events combine within a window. - 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 Status | Code | Meaning / Resolution |
|---|---|---|
| 400 | VALIDATION_ERROR | Malformed JSON, missing fields, batch size >100, occurred_at too far in future, etc. Fix payload. |
| 401 | UNAUTHORIZED | HMAC headers missing/incorrect. Recalculate signature, ensure timestamp is UTC seconds. |
| 403 | FORBIDDEN | Tenant is inactive/suspended. Contact Phoenix support. |
| 500 | INTERNAL_ERROR | Transient 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; returns503if downstream persistence is unavailable.- Emit logs/metrics on your side for each
event_idsent 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.