Skip to main content

Data Flow

This page explains how GPS position data and session events travel from a physical TRACKTICS tracker to your application — both in real time and as historical records.

End-to-end overview

Tracker (GPS device)

│ device WebSocket (firmware → backend)

TRACKTICS Backend

├──▶ WebSocket channels ──▶ Live clients (dashboards, apps)

└──▶ Position store ──▶ REST API (historical queries)
  1. A physical tracker continuously sends position packets to the TRACKTICS backend over a dedicated device WebSocket.
  2. The backend fans out each packet to any connected client that has subscribed to the relevant WebSocket channel.
  3. All position records are persisted. After a session ends, they are available for historical queries through the REST API.

When is data sent?

Position data is emitted whenever a tracker is powered on and connected. In practice this means:

  • Data starts flowing shortly before a session begins, once the trackers are switched on and have GPS lock.
  • Data continues throughout the session at a high frequency (multiple position samples per second, batched into packets).
  • Data stops when the trackers are switched off at the end of the session.

There is no explicit session-start trigger that gates data flow — the backend records everything the tracker sends and your application can filter by session time range if needed.

Markers (goals, events) are sent over the marker WebSocket channel the moment a coach creates them in the portal app.


WebSocket authentication

The WebSocket server is at:

wss://ws.dp.dev.tracktics.systems (staging)

Channels are selected by appending a path query parameter to the base WebSocket URL. Two authentication methods are supported:

Browsers cannot set custom HTTP headers on WebSocket connections, so TRACKTICS provides a one-time ticket mechanism:

Step 1 — Request a ticket

POST /ws/ticket
Authorization: Bearer <your-firebase-jwt>

Response

{
"ws_ticket_id": "f6a7b8c9-d0e1-2345-f123-456789012345"
}

The ticket is single-use and short-lived.

Step 2 — Connect with the ticket

wss://ws.dp.dev.tracktics.systems?ticket=<ws_ticket_id>&path=<channel-path>

Example:

wss://ws.dp.dev.tracktics.systems?ticket=f6a7b8c9-d0e1-2345-f123-456789012345&path=/position/clubs/a1b2c3d4-e5f6-7890-abcd-ef1234567890

Option 2 — Direct Bearer token (non-browser clients only)

If you control the WebSocket client (e.g., a native app or server-side process), you can pass the Firebase JWT directly in the HTTP upgrade request header:

Authorization: Bearer <your-firebase-jwt>

Then connect to:

wss://ws.dp.dev.tracktics.systems?path=<channel-path>

Quick test with wscat

wscat is a simple command-line WebSocket client useful for verifying channels without writing any code.

Replace <session_id> or <tracker_id> in the examples below with a real ID. See Exploring Your Club for step-by-step instructions on how to retrieve tracker and session IDs from the API.

Install

npm install -g wscat

Connect with a Bearer token (Option 2)

wscat \
--header "Authorization: Bearer <your-firebase-jwt>" \
--connect "wss://ws.dp.dev.tracktics.systems?path=/position/sessions/<session_id>"

Connect with a ticket (Option 1)

Obtain a ticket first, then connect in one pipeline:

TICKET=$(curl -s -X POST https://dp.stg.tracktics.systems/ws/ticket \
-H "Authorization: Bearer <your-firebase-jwt>" \
| jq -r '.ws_ticket_id')

wscat --connect "wss://ws.dp.dev.tracktics.systems?ticket=${TICKET}&path=/position/sessions/<session_id>"

Once connected, incoming JSON messages are printed to the terminal as position updates arrive.


WebSocket channels

Once connected, the server starts pushing JSON messages for the chosen channel. All channels are read-only — clients only receive, never send.

Club position channel

Receives position updates from all trackers currently active within the club.

path=/position/clubs/{club_id}

Use this channel for a club-wide live dashboard where you want to see every player on the same view.

Tracker position channel

Receives position updates for a single tracker.

path=/position/trackers/{tracker_id}

Use this channel when you need to track one specific device, independently of any session or club.

Session position channel

Receives position updates for all trackers assigned to a specific session.

path=/position/sessions/{session_id}

This is the most common channel for a live match or training view: you subscribe once to the session and receive data for all players participating.

Session marker channel

Receives marker notifications (goals, fouls, coach annotations) the moment they are created during a specific session.

path=/marker/sessions/{session_id}

Position message format

Every position message delivered on a position channel shares the same structure. A single packet can contain multiple samples, timestamped relative to each other using microsecond deltas to minimise bandwidth.

{
"device_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"club_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"tracker_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"position_id": "d4e5f6a7-b8c9-0123-def1-234567890123",
"sequence_number": 12345,
"timestamp": "2025-01-15T12:30:00Z",
"positions": [
{
"us_delta": 0,
"latitude": 37.7749,
"longitude": -122.4194,
"altitude": 100.0,
"speed": [5.2, 0.0, 0.0],
"attitude": [0.707, 0.0, 0.707, 0.0],
"acceleration": [0.1, 0.0, 9.8],
"relative_position": [10.5, 20.3]
},
{
"us_delta": 10000,
"latitude": 37.7750,
"longitude": -122.4195,
"altitude": 101.0,
"speed": [5.3, 0.0, 0.0],
"attitude": [0.710, 0.0, 0.704, 0.0],
"acceleration": [0.1, 0.0, 9.8],
"relative_position": [10.6, 20.4]
}
]
}
FieldDescription
tracker_idUUID of the tracker that produced this data
position_idUnique ID for this packet (use for deduplication)
sequence_numberMonotonically increasing counter per tracker
timestampISO 8601 timestamp of the first sample in the positions array
positions[].us_deltaMicroseconds since the previous sample (0 for the first sample)
positions[].latitude / longitudeWGS-84 coordinates
positions[].altitudeAltitude in metres
positions[].speedSpeed vector in m/s [x, y, z]
positions[].attitudeDevice orientation as a unit quaternion [w, x, y, z]
positions[].accelerationAcceleration in m/s² [x, y, z]
positions[].relative_positionPosition in metres relative to the centre of the field [x, y]

Marker message format

Messages on the marker channel follow the notification envelope:

{
"operation": "create",
"resource": "club/team/session/marker",
"timestamp": "2025-01-15T10:30:00Z",
"payload": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Goal",
"session_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"comment": "Great shot from the left wing",
"player_id": ["c3d4e5f6-a7b8-9012-cdef-123456789012"],
"user_id": "d4e5f6a7-b8c9-0123-def1-234567890123"
}
}

Historical position data via REST

After a session ends, all position records for a tracker are available through the standard collection endpoint:

GET /clubs/{club_id}/trackers/{tracker_id}/positions

This endpoint supports the standard pagination parameters (limit, next_token, order, from, until). Use the from and until parameters to narrow down the time range to a specific session.

GET /clubs/a1b2c3d4.../trackers/c3d4e5f6.../positions?from=2025-01-15T12:00:00Z&until=2025-01-15T14:00:00Z&order=asc
Authorization: Bearer <your-firebase-jwt>

Each item in the response has the same structure as a live WebSocket position message.