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)
- A physical tracker continuously sends position packets to the TRACKTICS backend over a dedicated device WebSocket.
- The backend fans out each packet to any connected client that has subscribed to the relevant WebSocket channel.
- 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:
Option 1 — Ticket-based (recommended for browsers)
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]
}
]
}
| Field | Description |
|---|---|
tracker_id | UUID of the tracker that produced this data |
position_id | Unique ID for this packet (use for deduplication) |
sequence_number | Monotonically increasing counter per tracker |
timestamp | ISO 8601 timestamp of the first sample in the positions array |
positions[].us_delta | Microseconds since the previous sample (0 for the first sample) |
positions[].latitude / longitude | WGS-84 coordinates |
positions[].altitude | Altitude in metres |
positions[].speed | Speed vector in m/s [x, y, z] |
positions[].attitude | Device orientation as a unit quaternion [w, x, y, z] |
positions[].acceleration | Acceleration in m/s² [x, y, z] |
positions[].relative_position | Position 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.