# WebSocket Payload Reference

This page documents all websocket endpoints and payloads currently emitted/accepted by `web_control_app.py`.

## Frame Envelope

Most frames follow this envelope:

```json
{
  "type": "frame_type",
  "data": { "...": "..." }
}
```

Some frames use top-level fields instead of `data` (for example `connection`, `graphics_state`, `preview_status`, `central_control_state`, `relay_live_json`, `relay_amcp`).

For `/ws/template`, server-side validation enforces required fields for selected frame types:

- `preview_command`: `data.action` required
- `live_timing_snapshot`: `data.session` and `data.rows` required
- `lap_counter_clock_sync`: `data.display_text` required
- `heartbeat`: `data.ts` required
- `template_payload`: top-level `element` required, top-level `data` must be an object

Unknown `type` values are allowed for forward compatibility.

## Endpoint Catalog

## 1) `/ws` (Control UI channel)

Auth required: yes.

### Outbound frames (server -> client)

Connection/bootstrap:

- `connection`
- `caspar_status`
- `central_control_status`
- `gfxsets`
- `timing_server`
- `session`
- `upcoming_session`
- `weather`
- `rc_message`
- `flag`
- `graphics_state`
- `preview_status`
- `graphics_session`
- `custom_lower_thirds`
- `chat_history`

Runtime snapshots:

- `live_timing_snapshot`
- `live_timing_rank_map`
- `track_map_snapshot`
- `commentator_snapshot`
- `live_control_snapshot`

Command/update bus:

- `preview_command`
- `template_payload`
- `ladder_config`
- `lap_counter_clock_sync`
- `fastest_lap_update`
- `race_results_ws_output`

Timing feed passthrough/events:

- `event`
- `session`
- `upcoming_session`
- `competitor`
- `grid`
- `result`
- `inter`
- `lap`
- `sector`
- `weather`
- `rc_message`
- `rc_messages_cleared`
- `flag_change`
- `data` (raw timing message passthrough wrapper)
- `timing_get_reply`

Chat/system:

- `chat_message`
- `keepalive_ack`

Request/response helpers over websocket:

- `graphics_command_result`
- `graphics_hide_all_result`
- `api_response`

### Inbound messages (client -> server)

- `chat_send`
- `graphics_command`
- `graphics_hide_all`
- `api_request`
- `preview_status_request` (also accepts `preview_state_request`, `status_request`)
- `graphic_state` (also accepts `graphics_state_report`, `template_graphic_state`)

### Key payload shapes

`live_timing_snapshot`:

- `data.session`: id, name, type, state, series, track, city, length_laps, length_time, is_race, series_logo_url, time_label
- `data.rows[]`: competitor timing rows including position/name/number, gap fields, sectors, flags/headshots/images, class fields, pit/penalty fields
- `data.speed_loops[]`: speed trap metadata for templates such as TOPSPEED

`live_timing_rank_map`:

```json
{
  "type": "live_timing_rank_map",
  "data": {
    "rank_map": [101, 12, 77],
    "position_mode": "overall",
    "updated_at": "2026-06-07T00:00:00+00:00"
  }
}
```

`track_map_snapshot`:

- `data.session`
- `data.map`
- `data.sectors[]`
- `data.speed_loops[]`
- `data.drivers[]`
- `data.avg_lap_time_us`
- `data.updated_at`

`commentator_snapshot`:

- `data.connection`
- `data.session`
- `data.weather`
- `data.race_control`
- `data.stats`
- `data.timing`
- `data.track_map`

`live_control_snapshot`:

- session context for current/selected session
- ladder rows for control UI
- class filters and timing summary
- sector slots and updated timestamp

`preview_command`:

```json
{
  "type": "preview_command",
  "schema_version": 1,
  "gfxset": "DEFAULT",
  "data": {
    "element": "race_results",
    "action": "show",
    "payload": {"...": "..."},
    "layer": 389,
    "template_path": "DEFAULT/RACERESULTS/INDEX.html",
    "template_url": "/cg-templates/DEFAULT/RACERESULTS/INDEX.html?v=...",
    "gfxset": "DEFAULT",
    "timestamp": "2026-06-07T00:00:00+00:00"
  }
}
```

`template_payload`:

```json
{
  "type": "template_payload",
  "schema_version": 1,
  "gfxset": "DEFAULT",
  "element": "top_speed",
  "data": {"...": "element-specific payload ..."},
  "updated_at": "2026-06-07T00:00:00+00:00"
}
```

`lap_counter_clock_sync`:

- `data.time_remaining_us`
- `data.clock_running`
- `data.session_state`
- `data.format` (`time`)
- `data.display_text`
- `data.flag_text`

`fastest_lap_update`:

- `data`: fastest lap payload (driver identity, lap time, headshot/class color)
- `source`: update source (for example race or pole-lap flow)

`race_results_ws_output`:

- `data[]` rows with:
  - `CompetitorID` / `competitorId`
  - `car_id` / `CarId`
  - `position`
  - `driverName`
  - `teamName`
  - `StartNumber` / `Number` / `DriverNumber`
  - `FlagUrl`
  - `HeadshotUrl`
  - `PhotoUrl`
  - `BodyUrl`
  - `ImageUrl`
  - `lapTime`
  - `gapSeconds`
  - `lapsBehind`
  - `displayGap`
  - `status`
  - `BestLap`
  - `LastLap`

`timing_get_reply`:

- `msg_type`: requested timing message type
- `payload`: raw timing payload received
- `received_at`

`timing_server`:

- `reason`: why timing endpoint state changed/restarted
- `data`: timing endpoint config/state payload

`ladder_config`:

- `config`: live ladder rendering config (timing mode, identity mode, position mode, class filter, rotate flags, display toggles)

## 2) `/ws/template` (Template render channel)

Auth required: no.

### Outbound frames (server -> template)

Initial bootstrap on connect:

- `live_timing_snapshot`
- `live_timing_rank_map`
- `template_payload` for retained static snapshots (currently `race_results`)

Ongoing frames:

- `live_timing_snapshot`
- `live_timing_bootstrap_snapshot` (one-off when ladder is shown)
- `live_timing_rank_map`
- `template_payload`
- `preview_command`
- `ladder_config`
- `lap_counter_clock_sync`
- `heartbeat`
- `fastest_lap_update`
- `race_results_ws_output`

### Inbound messages (template -> server)

- `graphic_state` (or `graphics_state_report` / `template_graphic_state`)

Accepted fields:

- `element` or `graphic`
- either `on_air` (boolean-like) or `state` (`on`, `active`, `visible`, etc.)

This updates server-side active element state and may trigger a `graphics_state` broadcast to `/ws` clients.

## 3) `/ws/template-state` (Template state-only channel)

Auth required: no.

### Outbound

- none (no regular broadcasts)

### Inbound

- `graphic_state` (or `graphics_state_report` / `template_graphic_state`)

Purpose: lightweight on-air/off-air state reporting from templates.

## 4) `/ws/central-control` (Flat state compatibility channel)

Auth required: yes.

### Outbound frames

- `central_control_state` (on connect and periodic updates)
- `keepalive_ack` (for keepalive/ping style requests)
- text response `pong` for text `ping` messages

`central_control_state` includes top-level aliases plus nested sections for graphics, flag, session, caspar, timing.

### Inbound messages

Text commands:

- `ping`, `keepalive`, `heartbeat`
- `get_state`, `state`, `snapshot`

JSON commands (via `type` or `action`):

- `ping`, `keepalive`, `heartbeat`
- `get_state`, `state`, `snapshot`, `state_request`

## 5) `/ws/relay-agent` (Relay bridge channel)

Auth required: yes (signed query params).

### Outbound frames

- `ready`
- `relay_live_json` (bootstrap + ongoing)
- `relay_amcp` (bootstrap commands + ongoing command stream)

### Inbound

- keepalive text accepted/ignored

`relay_live_json.live_json` contains: gfxset, session, event, weather, flag, active_elements, payloads, generated_at.

## Payload Notes

- `schema_version` currently equals 1 for template-facing command/data envelopes.
- Template websocket URL aliases are provided in relevant payloads so templates running in CasparCG (`file://`) can connect to websocket endpoints.
- `data` passthrough frames are emitted for every parsed timing `DATA <MessageType> <json>` line and include:
  - `message_type`
  - `payload`

## Quick Examples

Template payload update frame:

```json
{
  "type": "template_payload",
  "schema_version": 1,
  "gfxset": "DEFAULT",
  "element": "race_results",
  "data": {"Title": "RACE RESULTS", "Competitors": []},
  "updated_at": "2026-06-07T12:34:56.789012+00:00"
}
```

Heartbeat frame:

```json
{
  "type": "heartbeat",
  "data": {
    "ts": 1760000000.123,
    "seq": 42,
    "interval_s": 2.0,
    "active_gfxset": "DEFAULT",
    "feed_connected": true,
    "live_session_id": 12345
  }
}
```
