WebSockets (Live)
Real-time push for stat events, status changes, and scores. Uses the Pusher protocol (our server is Laravel Reverb), so existing SDKs work out of the box.
Connection config
| Host | stats-api.ds2.app |
| Port | 443 (wss, through the nginx proxy) |
| App key | stats-admin-public-key |
| Protocol | Pusher 7.x |
| Auth endpoint | https://stats-api.ds2.app/api/v1/broadcasting/auth |
The app key is public — security comes from the secret Bearer token sent to the auth endpoint.
Channels
All channels are private — you must be subscribed to the relevant competition.
| Channel | What it sends | Access condition |
|---|---|---|
private-match.{id} | .event.received (every shot/rebound/...), .status.changed | Subscription to the match's competition |
private-competition.{id} | .status.changed (when a match changes status) | Subscription to that competition |
private-games | Every .event.received and .status.changed globally | Any active account (good for tickers) |
Event types
.event.received
A new stat event was recorded (shot, rebound, assist, etc.). Payload:
json
{
"match_id": 123,
"home_score": 45,
"away_score": 42,
"current_period": 2,
"current_clock": "07:23",
"status": "in_progress",
"event": {
"id": 9871,
"client_id": "7b2a...uuid",
"type": "3pt_made",
"team_id": 16,
"player_id": 88,
"period": 2,
"clock": "07:23",
"x": 42.5,
"y": 18.2,
"event_at": "2026-04-13T20:15:30+00:00"
}
}.status.changed
json
{
"match_id": 123,
"competition_id": 4,
"status": "finished",
"previous_status": "in_progress",
"home_score": 89,
"away_score": 84
}Setup (Node.js / browser)
bash
npm install laravel-echo pusher-jsecho-client.jsjavascript
import Echo from 'laravel-echo'
import Pusher from 'pusher-js'
// Pusher must be on window for laravel-echo to find it
window.Pusher = Pusher
export const echo = new Echo({
broadcaster: 'reverb',
key: 'stats-admin-public-key',
wsHost: 'stats-api.ds2.app',
wssPort: 443,
forceTLS: true,
enabledTransports: ['ws', 'wss'],
disableStats: true,
// Private channel auth — sends our Bearer token to the server
authEndpoint: 'https://stats-api.ds2.app/api/v1/broadcasting/auth',
auth: {
headers: {
Authorization: 'Bearer ' + process.env.STATS_API_TOKEN,
},
},
})Example: follow one match
javascript
import { echo } from './echo-client'
const channel = echo.private('match.123')
channel.listen('.event.received', (data) => {
console.log(`Score: ${data.home_score} : ${data.away_score}`)
console.log(`Last event: ${data.event.type}`)
updateScoreboard(data)
})
channel.listen('.status.changed', (data) => {
if (data.status === 'finished') {
showFinalResult(data)
}
})
// When you are done watching:
echo.leave('match.123')Example: global ticker
javascript
const ticker = echo.private('games')
ticker.listen('.event.received', (data) => {
// Every event from every match in the system
refreshTickerCard(data.match_id, data.home_score, data.away_score)
})
ticker.listen('.status.changed', (data) => {
// Live dot updates the status in the ticker carousel
refreshTickerStatus(data.match_id, data.status)
})Reconnect and error handling
The Pusher client reconnects automatically. For robustness, add handlers:
javascript
echo.connector.pusher.connection.bind('error', (err) => {
console.error('WS error:', err)
})
echo.connector.pusher.connection.bind('connected', () => {
console.log('WS connected — refetch state from HTTP API to catch missed updates')
// Re-fetch box score / standings via /api/v1 to backfill the gap
})
echo.connector.pusher.connection.bind('disconnected', () => {
console.warn('WS disconnected — pusher-js will auto-reconnect')
})PHP / Python / other languages
The Pusher protocol has official SDKs for most languages. The config is always the same:key=stats-admin-public-key, host=stats-api.ds2.app,port=443, tls=true, auth endpoint =/api/v1/broadcasting/auth with a Bearer header.
- PHP:
pusher/pusher-php-client— rarely needed (server-side PHP rarely subscribes) - Python:
pusherclientorpysher - Go:
pusher-http-go - Java/Kotlin:
pusher-java-client