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

# WebSocket Guide

> Real-time event subscriptions via WebSocket

## Connection

```
wss://api.xshot.fun/ws?api_key=YOUR_KEY
```

On connect, the server sends:

```json theme={null}
{
  "type": "connected",
  "timestamp": "2026-03-28T13:39:50.995Z"
}
```

## Subscribe to a Channel

Send a JSON message:

```json theme={null}
{
  "type": "subscribe",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" },
  "interval": 15
}
```

**Server confirms:**

```json theme={null}
{
  "type": "subscribed",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" },
  "interval": 15
}
```

## Receive Events

When data changes, the server pushes an event. Only changed fields are included — no event is sent if nothing changed.

**Example — tweet gets 2 new views:**

```json theme={null}
{
  "type": "event",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" },
  "data": {
    "view_count": 41836,
    "view_count_delta": 2
  },
  "timestamp": "2026-03-28T13:40:14.239Z"
}
```

## Unsubscribe

```json theme={null}
{
  "type": "unsubscribe",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" }
}
```

**Server confirms:**

```json theme={null}
{
  "type": "unsubscribed",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" }
}
```

## Available Channels

| Channel            | Description                 | Required Params | Default Interval | Min/Max |
| ------------------ | --------------------------- | --------------- | ---------------- | ------- |
| `user:tweets`      | New tweets from a user      | `username`      | 30s              | 15-120s |
| `user:mentions`    | Tweets mentioning a user    | `username`      | 30s              | 15-120s |
| `user:followers`   | Follower count changes      | `username`      | 60s              | 30-300s |
| `tweet:engagement` | Live likes, retweets, views | `tweet_id`      | 15s              | 10-120s |
| `search:tweets`    | New tweets matching a query | `query`         | 30s              | 15-120s |

## Event Data Formats

### user:tweets

New tweets from a specific user.

```json theme={null}
{
  "type": "event",
  "channel": "user:tweets",
  "params": { "username": "elonmusk" },
  "data": {
    "new_tweets": [
      {
        "id": "2037850934274138146",
        "text": "Starship launch window opens tomorrow",
        "created_at": "Fri Mar 28 14:30:00 +0000 2026",
        "author": {
          "id": "44196397",
          "name": "Elon Musk",
          "username": "elonmusk",
          "profile_image": "https://pbs.twimg.com/profile_images/.../normal.jpg",
          "verified": false,
          "followers_count": 237614000
        },
        "like_count": 45000,
        "retweet_count": 8200,
        "reply_count": 3100,
        "view_count": 12000000
      }
    ]
  },
  "timestamp": "2026-03-28T14:30:30.000Z"
}
```

### user:mentions

New tweets mentioning a user.

```json theme={null}
{
  "type": "event",
  "channel": "user:mentions",
  "params": { "username": "elonmusk" },
  "data": {
    "new_mentions": [
      {
        "id": "2037851000000000000",
        "text": "@elonmusk when is the next Starship launch?",
        "created_at": "Fri Mar 28 14:35:00 +0000 2026",
        "author": {
          "id": "999999999",
          "name": "Space Fan",
          "username": "spacefan42",
          "profile_image": "https://pbs.twimg.com/profile_images/.../normal.jpg",
          "verified": false,
          "followers_count": 1500
        },
        "like_count": 5,
        "retweet_count": 0,
        "reply_count": 1,
        "view_count": 200
      }
    ]
  },
  "timestamp": "2026-03-28T14:35:30.000Z"
}
```

### user:followers

Follower count changes. Reports the new count, previous count, and delta.

```json theme={null}
{
  "type": "event",
  "channel": "user:followers",
  "params": { "username": "elonmusk" },
  "data": {
    "followers_count": 237614746,
    "previous_count": 237614000,
    "change": 746
  },
  "timestamp": "2026-03-28T14:31:00.000Z"
}
```

### tweet:engagement

Live engagement metrics. Only changed fields are included, each with its delta.

```json theme={null}
{
  "type": "event",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" },
  "data": {
    "like_count": 328,
    "like_count_delta": 2,
    "view_count": 41836,
    "view_count_delta": 985,
    "retweet_count": 70,
    "retweet_count_delta": 2
  },
  "timestamp": "2026-03-28T13:40:14.239Z"
}
```

### search:tweets

New tweets matching a search query.

```json theme={null}
{
  "type": "event",
  "channel": "search:tweets",
  "params": { "query": "bitcoin" },
  "data": {
    "new_tweets": [
      {
        "id": "2037852000000000000",
        "text": "Bitcoin breaking through resistance levels today",
        "created_at": "Fri Mar 28 15:00:00 +0000 2026",
        "author": {
          "id": "888888888",
          "name": "Crypto Analyst",
          "username": "cryptoanalyst",
          "profile_image": "https://pbs.twimg.com/profile_images/.../normal.jpg",
          "verified": false,
          "followers_count": 50000
        },
        "like_count": 120,
        "retweet_count": 30,
        "reply_count": 15,
        "view_count": 25000
      }
    ]
  },
  "timestamp": "2026-03-28T15:00:30.000Z"
}
```

## Error Events

Sent when polling fails (e.g. rate limit, account issues):

```json theme={null}
{
  "type": "error",
  "channel": "tweet:engagement",
  "params": { "tweet_id": "2035548674483273831" },
  "code": "POLL_ERROR",
  "message": "Rate limited (429)"
}
```

Error codes: `INVALID_JSON`, `INVALID_CHANNEL`, `SUBSCRIBE_FAILED`, `POLL_ERROR`, `UNKNOWN_TYPE`

## Keepalive

Send periodic pings to keep the connection alive:

```json theme={null}
{ "type": "ping" }
```

```json theme={null}
{ "type": "pong" }
```

The server also sends WebSocket-level pings every 30 seconds. Unresponsive clients are disconnected after 10 seconds.

## Full JavaScript Example

```javascript theme={null}
const ws = new WebSocket("wss://api.xshot.fun/ws?api_key=YOUR_KEY");

ws.onopen = () => {
  console.log("Connected to Xshot WebSocket");

  // Monitor tweet engagement in real-time
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "tweet:engagement",
    params: { tweet_id: "2035548674483273831" },
    interval: 15
  }));

  // Watch for new tweets from a user
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "user:tweets",
    params: { username: "elonmusk" },
    interval: 30
  }));

  // Monitor follower count changes
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "user:followers",
    params: { username: "elonmusk" },
    interval: 60
  }));
};

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

  switch (msg.type) {
    case "connected":
      console.log("Server ready:", msg.timestamp);
      break;

    case "subscribed":
      console.log(`Subscribed to ${msg.channel} (polling every ${msg.interval}s)`);
      break;

    case "event":
      console.log(`[${msg.channel}]`, JSON.stringify(msg.data, null, 2));
      break;

    case "error":
      console.error(`Error on ${msg.channel}: ${msg.message}`);
      break;
  }
};

ws.onclose = (event) => {
  if (event.code === 4001) {
    console.error("Authentication failed - check your API key");
  } else {
    console.log("Disconnected:", event.code, event.reason);
  }
};
```

## Full Python Example

```python theme={null}
import asyncio
import json
import websockets

API_KEY = "YOUR_KEY"

async def main():
    uri = f"wss://api.xshot.fun/ws?api_key={API_KEY}"

    async with websockets.connect(uri) as ws:
        # Subscribe to follower changes
        await ws.send(json.dumps({
            "type": "subscribe",
            "channel": "user:followers",
            "params": {"username": "elonmusk"},
            "interval": 60
        }))

        async for message in ws:
            msg = json.loads(message)

            if msg["type"] == "event":
                data = msg["data"]
                print(f"Followers: {data['followers_count']} ({data['change']:+d})")

            elif msg["type"] == "error":
                print(f"Error: {msg['message']}")

asyncio.run(main())
```

## Behavior Details

* **Deduplication**: Multiple clients on the same resource share one poll
* **First poll**: Captures initial snapshot without emitting events (avoids flood)
* **Auto-cleanup**: Polling stops when all clients unsubscribe from a resource
* **Max subscriptions**: 100 total across all connected clients
* **Snapshot cap**: 500 IDs per subscription to prevent memory growth
* **Error backoff**: After 5 consecutive poll failures, an error event is sent to all subscribers
