Live results & score tracking · E-Arena

Meet Pulse

The ingestion and read API behind eArena's live scoreboards. Pulse accepts signed result webhooks, normalizes them per game, and serves tournament matches & leaderboards over a bearer-secured API.


How Pulse works

Webhook in, leaderboard out.

A small Fastify + Postgres service that turns raw match results from external sources into clean, queryable tournament data.

01 · INTAKE

Signed webhooks

External result sources POST to /webhooks/results. Every payload is HMAC-signed and verified before it's accepted.

02 · NORMALIZE

Game adapters

A pluggable adapter registry maps each game's raw result into a shared schema. chess ships today; more games slot in cleanly.

03 · SERVE

Read API

Query tournament matches and leaderboards over a clean REST API, secured with a bearer token. Validated with Zod.

Built with Fastify Zod · type-provider-zod Drizzle ORM Postgres OpenAPI · Swagger UI Vitest

Authentication

Get an access token.

The read API is secured with a bearer token. Generate a development token below, then send it as an Authorization header on every request.

Token generator

Read scopes power scoreboard overlays & casters. Write scope lets a source push signed results.

eap_live_…
Scope results:read Expires in 90 days Env development
Demo token — generated in your browser for previewing the flow. For a real key, request one from the eArena platform team.
Use it on every request
curl — fetch a leaderboard
curl -H "Authorization: Bearer $PULSE_TOKEN" \
  https://pulse.cloud.earena.no/api/v1/tournaments/spring-open/leaderboard
200 OK — application/json
{
  "tournament": {
    "id": "4e6f1c2a-9b3d-4f21-8e6a-7c1b2d3e4f5a",
    "sanityId": "spring-open",
    "name": "spring-open"
  },
  "standings": [
    { "key": "m.carlsen", "displayName": "m.carlsen", "totalScore": "8.500", "matchesPlayed": 9 },
    { "key": "h.nakamura", "displayName": "h.nakamura", "totalScore": "8.000", "matchesPlayed": 9 }
  ]
}

API reference

Endpoints.

The full machine-readable contract lives in the OpenAPI spec. Here are the endpoints you'll reach for most — click any row to expand.

POST /webhooks/results Ingest a signed match result HMAC signed

Entry point for external result sources. Requests are verified against the X-Pulse-Source, X-Pulse-Timestamp and X-Pulse-Signature headers, then routed through the matching game adapter and persisted.

Headers
X-Pulse-Source string Name of a registered webhook source. Required.
X-Pulse-Timestamp string Unix timestamp in seconds; must be within 5 minutes of server time. Required.
X-Pulse-Signature string sha256=<hmac> — HMAC-SHA256 of ${timestamp}.${rawBody} using the source's secret. Required.
Body — application/json
gameType string Game adapter key, e.g. chess.
tournamentSanityId string? Sanity document id of the tournament.
externalMatchId string Unique id for this match from the source system.
status string scheduled | live | completed.
result object Game-specific result payload, validated by the matching adapter.
GET /api/v1/tournaments/:sanityId/matches List matches for a tournament Bearer

Returns the tournament and all of its matches, each with their participants, ordered by start time.

Path parameters
sanityId string Sanity document id of the tournament.
GET /api/v1/matches/:id Fetch a single match Bearer

Returns a single match together with its participants.

Path parameters
id string (uuid) The match id, as returned by /api/v1/tournaments/:sanityId/matches.
GET /api/v1/tournaments/:sanityId/leaderboard Computed standings for a tournament Bearer

Returns aggregated standings — total score and matches played per participant — ordered by total score descending. This is what live scoreboard overlays poll.

Path parameters
sanityId string Sanity document id of the tournament, e.g. spring-open.
GET /health Liveness check Public

No auth required. Returns { "status": "ok" } when the service is running. Does not check database connectivity.


OpenAPI · Swagger UI

The interactive docs.

Every route, schema and example is generated from the running service via @fastify/swagger. Try requests live, with your token, right in the browser.

Copied to clipboard