> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ticktock.bet/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket Stream

> Real-time CS2 events filtered by your stream scopes

OpenAPI doesn't describe WebSocket endpoints — this page is the contract.

## Endpoints

```
wss://ticktock.bet/cs2/v1/stream?api_key=<key>           # all matches
wss://ticktock.bet/cs2/v1/stream/{match_id}?api_key=<key> # one match
```

Browsers can't set custom headers on the WS upgrade, so the key rides as a query parameter.

## Auth and scope filtering

The handshake validates `api_key`. After upgrade, the server pushes only the message types your key's scopes allow:

| Frame type                                                                      | Required scope          |
| ------------------------------------------------------------------------------- | ----------------------- |
| `match.created`, `match.updated`, `match.finished`                              | `cs2:stream:matches`    |
| `round.started`, `round.finished`                                               | `cs2:stream:matches`    |
| `event.kill`, `event.bomb_planted`, `event.bomb_defused`, `event.bomb_exploded` | `cs2:stream:matches`    |
| `market.created`, `market.updated`, `market.suspended`, `market.unsuspended`    | `cs2:stream:markets`    |
| `bet_settlement`                                                                | `cs2:stream:markets`    |
| `scoreboard.frame`                                                              | `cs2:stream:scoreboard` |

Frames you aren't entitled to are silently dropped — no `403` per-frame, no errors. Use `/v1/whoami` to confirm your stream scopes before debugging missing frames.

## Frame envelope

```json theme={null}
{
  "type": "match.updated",
  "data": { "id": "…", "status": "live", "score_a": 8, "score_b": 5, … },
  "meta": { "ts": "2026-05-10T13:42:11.123Z", "seq": 481234 }
}
```

`data` is the same shape (and same scope-filtered field set) as the matching REST endpoint — `match.updated` mirrors `GET /cs2/v1/matches/{id}`, `bet_settlement` mirrors a settlement entry, etc.

## Snapshot semantics

On connect the server sends a snapshot of currently-relevant state (live matches, active markets) before switching to incremental frames. After that it polls and emits a frame only when the underlying state actually changes — no constant chatter.

| Channel                              | Default poll cadence |
| ------------------------------------ | -------------------- |
| `/cs2/v1/stream` (list)              | 5s                   |
| `/cs2/v1/stream/{match_id}` (detail) | 2s                   |

Frames carry a content-hash; if nothing changed in the polling tick, nothing is pushed.

## Frame format

WebSocket frames are JSON only. The REST surface offers XML for the seven feed endpoints (see [Content negotiation](/getting-started/scopes#content-negotiation)); the WS channel does not — frames are always JSON regardless of the `Accept` header on the upgrade.

Clients that need XML for their archival pipeline can hit the matching REST endpoint with `Accept: application/xml` after each interesting frame.

## Keep-alive

The server sends an `alive` envelope every 10s — same shape as REST `/v1/alive`. If you don't see one for \~30s, reconnect.

## Reconnect strategy

* Reconnect with exponential backoff (start 1s, cap at 30s).
* After reconnect, kick off a [stateful recovery replay](/api-reference/recovery) — the AMQP path republishes the current market state plus settlements/voids since your last processed timestamp and terminates with `<snapshot_complete>`.
* Unique-ish frame ids live in `meta.seq` — use it to dedupe across reconnects.

## Example session

```bash theme={null}
wscat -c "wss://ticktock.bet/cs2/v1/stream?api_key=ttk_live_…"
< { "type": "match.updated", "data": { "id": "…", "score_a": 8, … }, "meta": {...} }
< { "type": "market.updated", "data": { "id": "…", "odds": [...], "status": 1 }, "meta": {...} }
< { "type": "round.finished", "data": { "match_id": "…", "round_number": 14, "winner_side": "CT" }, "meta": {...} }
< { "type": "bet_settlement", "data": { "id": "…", "match_id": "…", "result": "won" }, "meta": {...} }
```
