Twitch integration
Braket integrates with Twitch so that players and casters can link their channel, viewers can be alerted when followed players go live, and live matches surface a "watch now" link. The integration is deliberately read-only: Braket verifies channel identity and polls live status; it never re-streams or takes broadcast control. This chapter covers the single network-wide Twitch app, the one OAuth redirect URL you register, the environment configuration, the scope, Helix live-status polling, follows and alerts, and the caster directory. It is accurate to src/twitch/index.ts and src/web/routes/twitch.ts.
One network-wide Twitch app
There is a single Twitch application for the entire Braket network, not one per arena. Every arena's linking and live-status checks go through the same app credentials. This is why there is only one redirect URL to register and one pair of credentials to configure at the platform level.
The single OAuth redirect URL
Register exactly one OAuth redirect URL in the Braket Twitch app:
https://braket.gg/twitch/callback
This is derived from the platform domain (https://<platform-domain>/twitch/callback, where the platform domain defaults to braket.gg). Because linking must work for every arena but Twitch only allows a fixed set of redirect URIs, Braket routes all link flows through this one centralized callback: when a player starts linking, the server stashes a random state mapped to (tenant, user), redirects the player to Twitch, and when Twitch calls back to https://braket.gg/twitch/callback, Braket looks up the state to reattach the verified channel to the right player in the right arena.
Environment configuration
Real-mode Twitch needs Helix app credentials:
| Variable | Meaning |
|---|---|
TWITCH_CLIENT_ID |
The Braket Twitch app's client id |
TWITCH_CLIENT_SECRET |
The Braket Twitch app's client secret |
When both are set, the real Twitch service runs. When either is missing, Braket runs the mock twin: linking asserts a login directly with no external call, and live status is an in-memory set (tests and dev flip it directly). This mirrors the mock-first approach used for Steam, so you can develop the whole feature with no Twitch account.
Read-only scope
The OAuth authorize request uses an empty scope: identity only, no permissions. Braket only needs to learn the channel login name, so it requests no re-streaming, chat, or channel-management scopes. The authorize URL is:
https://id.twitch.tv/oauth2/authorize?client_id=…&redirect_uri=https://braket.gg/twitch/callback&response_type=code&scope=&state=…
The code is exchanged at https://id.twitch.tv/oauth2/token (authorization-code grant), and the resulting token is used once against https://api.twitch.tv/helix/users to read the login, which is lowercased and stored on the player's users.twitchLogin. Nothing else about the channel is read or retained.
Linking flow
- Real mode.
POST /twitch/link(signed-in player) stashes astatemapping to the tenant and user, then redirects to the Twitch authorize URL. Twitch calls back tohttps://braket.gg/twitch/callback; Braket exchanges the code for the login and setstwitchLoginon the player. - Mock mode.
POST /twitch/linktakes the desiredloginstraight from the form and sets it, with zero external calls. - Unlink.
POST /twitch/unlinkclears the player'stwitchLogin.
Helix live-status polling
Live status comes from Helix GET /helix/streams, using an app access token obtained via the client-credentials grant (cached until shortly before expiry). Braket batches lookups (Helix allows up to 100 logins per query) and returns the subset of the requested logins that are currently live.
To avoid hammering Helix on page renders, results are cached per tenant for 60 seconds (cachedLiveLogins). A failed Helix call degrades gracefully to "no one live" rather than erroring the page. So live badges across an arena reflect Twitch state with at most a 60-second lag.
Streamer follows and alerts
Players can follow a tournament or a player to be alerted around matches and when a followed player goes live. Follows are stored in the follows table (kind is tournament or player, plus a targetId), one row per user/kind/target.
POST /follow/tournament/:idfollows (or, withoff=on, unfollows) a tournament.POST /follow/player/:idfollows (or unfollows) a player.
Alerts are driven from these follows: upcoming-match notifications (stamped on the match as upcomingAlertAt so the sweep never double-fires), live alerts, and "a player you follow is streaming" alerts fed by the Helix live-status poll.
Match POV streams and the caster directory
There are two distinct streaming concepts:
- Participant POV streams. A participant can advertise or retire their own POV stream on a match with
POST /matches/:id/stream(toggle withoff=on). These are recorded inmatch_streams(matchId,userId,twitchLogin), one per user per match, and combined with the 60-second Helix live cache to show which POVs are live right now. - Casters (Mode-Obs). A match can have several casters, recorded in
match_casters: openly claimed by the community, invited by a player, or the arena's official channel attached by an admin. Every active caster is surfaced to the game client in the live-match discovery payload (thecastersarray, pluscasterSteamId/streamUrlmirroring the first, andallowSpectators), so your game can show who is casting and where to watch. Casters work from the match recording (VOD, or near-live segments as they release) rather than a live in-game spectator feed (see The game-client result protocol (dual attestation)).
Together, linked channels, live status, follows, and match streams populate a caster directory: which channels are casting which matches, who is live, and where to watch, all within an arena. Because the integration is identity-plus-live-status only, nothing here gives Braket control over anyone's broadcast.