> For the complete documentation index, see [llms.txt](https://info.soccerfootball.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://info.soccerfootball.info/websocket/channels/stats-realtime.md).

# STATS REALTIME

The STATS\_REALTIME channel delivers only the statistics that have changed, in real-time.

### Availability

| Plan  | Available |
| ----- | --------- |
| BASIC | No        |
| PRO   | No        |
| ULTRA | No        |
| MEGA  | Yes       |

### Update Frequency

Updates are sent **immediately** when statistics change (real-time).

### Events

| Event                   | Description             |
| ----------------------- | ----------------------- |
| stats\_update\_realtime | Changed statistics only |

***

### stats\_update\_realtime

Delivers only the statistics that have changed since the last update.

#### Message

```json
{
  "type": "stats_update_realtime",
  "channel": "STATS_REALTIME",
  "matchId": "12345",
  "leagueId": "67890",
  "ts": 1705314600000,
  "data": {
    "changedStats": [
      {
        "stat": "possession",
        "value": "56;44"
      },
      {
        "stat": "corners",
        "value": "5;2"
      },
      {
        "stat": "shotsOnTarget",
        "value": "5;2"
      }
    ]
  }
}
```

#### Data Fields

| Field                  | Type   | Description                       |
| ---------------------- | ------ | --------------------------------- |
| `changedStats`         | array  | Array of changed statistics       |
| `changedStats[].stat`  | string | Statistic name                    |
| `changedStats[].value` | string | New value in "teamA;teamB" format |

#### Available Statistics

| Stat Name          | Description          |
| ------------------ | -------------------- |
| `timer`            | Current match minute |
| `extraTimer`       | Extra time minutes   |
| `status`           | Match status         |
| `score`            | Current score        |
| `score1h`          | First half score     |
| `score2h`          | Second half score    |
| `possession`       | Ball possession %    |
| `attacks`          | Total attacks        |
| `dangerousAttacks` | Dangerous attacks    |
| `shotsOnTarget`    | Shots on target      |
| `shotsOffTarget`   | Shots off target     |
| `totalShots`       | Total shots          |
| `shotsBlocked`     | Blocked shots        |
| `corners`          | Total corners        |
| `corners1h`        | First half corners   |
| `fouls`            | Fouls committed      |
| `yellowCards`      | Yellow cards         |
| `redCards`         | Red cards            |
| `yellowToRed`      | Second yellow cards  |
| `substitutions`    | Substitutions        |
| `throwIns`         | Throw-ins            |
| `injuries`         | Injuries             |
| `penalties`        | Penalties awarded    |
| `offsides`         | Offsides             |
| `xg`               | Expected goals       |

***

### Subscription Examples

#### Subscribe to all real-time stats updates

NO scope "all" allowed

#### Subscribe to a specific match

```json
{"action": "subscribe", "channel": "STATS_REALTIME", "scope": "match", "id": "b01d28ac92221034"}
```

#### Subscribe to a league

```json
{"action": "subscribe", "channel": "STATS_REALTIME", "scope": "league", "id": "f2b16fa54cb525d"}
```

***

### Example: Maintaining Local State

```javascript
// Local state for match statistics
const matchStats = {};

function initializeStats(matchId) {
  matchStats[matchId] = {
    timer: '0',
    possession: '50;50',
    corners: '0;0',
    shotsOnTarget: '0;0',
    shotsOffTarget: '0;0',
    fouls: '0;0',
    yellowCards: '0;0',
    redCards: '0;0'
    // ... other stats initialized to defaults
  };
}

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type !== 'stats_update_realtime') return;

  const matchId = msg.matchId;

  // Initialize if first update for this match
  if (!matchStats[matchId]) {
    initializeStats(matchId);
  }

  // Apply delta updates
  msg.data.changedStats.forEach(change => {
    const oldValue = matchStats[matchId][change.stat];
    matchStats[matchId][change.stat] = change.value;

    console.log(`[${matchId}] ${change.stat}: ${oldValue} → ${change.value}`);
  });

  // Now matchStats[matchId] contains the current state
  console.log('Current stats:', matchStats[matchId]);
};
```

### Example: Detecting Specific Changes

```javascript
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type !== 'stats_update_realtime') return;

  msg.data.changedStats.forEach(change => {
    switch (change.stat) {
      case 'corners':
        const [homeCorners, awayCorners] = change.value.split(';').map(Number);
        console.log(`Corner! Home: ${homeCorners}, Away: ${awayCorners}`);
        break;

      case 'yellowCards':
        const [homeYellow, awayYellow] = change.value.split(';').map(Number);
        console.log(`Yellow card! Home: ${homeYellow}, Away: ${awayYellow}`);
        break;

      case 'redCards':
        const [homeRed, awayRed] = change.value.split(';').map(Number);
        if (homeRed > 0 || awayRed > 0) {
          console.log(`RED CARD! Home: ${homeRed}, Away: ${awayRed}`);
        }
        break;

      case 'possession':
        const [homePoss, awayPoss] = change.value.split(';').map(Number);
        console.log(`Possession update: ${homePoss}% - ${awayPoss}%`);
        break;
    }
  });
};
```

***

### Comparison: STATS vs STATS\_REALTIME

| Feature           | STATS         | STATS\_REALTIME |
| ----------------- | ------------- | --------------- |
| Update frequency  | 30-60 seconds | Real-time       |
| Data sent         | Full snapshot | Delta only      |
| Bandwidth usage   | Higher        | Lower           |
| State management  | Not required  | Required        |
| Plan availability | ULTRA, MEGA   | MEGA only       |

### Best Practices

1. **Maintain local state**: Keep a local copy of statistics and apply deltas
2. **Handle reconnections**: On reconnect, subscribe to STATS first for a full snapshot, then switch to STATS\_REALTIME
3. **Combine channels**: Use STATS for initial state and STATS\_REALTIME for updates
4. **Handle missing matches**: Initialize stats when you receive the first update for a match

### Notes

* Only changed statistics are included in each message
* An empty `changedStats` array means no changes (rare, but possible)
* For a complete snapshot, use the STATS channel
* This channel requires client-side state management
