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

# WebSocket

> Full-duplex real-time streaming via WebSocket

Connect to `wss://scrape.st/ws` for bidirectional, real-time data streaming across all sources. WebSocket delivers full `SourceEvent` payloads for every tracked source.

## Connection

<CodeGroup>
  ```javascript Node.js theme={null}
  import WebSocket from "ws";

  const ws = new WebSocket("wss://scrape.st/ws", {
    headers: { "x-api-key": "YOUR_API_KEY" },
  });

  ws.on("open", () => console.log("Connected"));
  ws.on("message", (raw) => {
    const event = JSON.parse(raw.toString());
    console.log(`[${event.source}] ${event.mid}`, event.payload);
  });
  ws.on("close", () => console.log("Disconnected"));
  ws.on("error", (e) => console.error("Error:", e.message));
  ```

  ```javascript Browser theme={null}
  const ws = new WebSocket("wss://scrape.st/ws?apiKey=YOUR_API_KEY");

  ws.onopen = () => console.log("Connected");
  ws.onmessage = (e) => {
    const event = JSON.parse(e.data);
    console.log(`[${event.source}] ${event.mid}`, event.payload);
  };
  ws.onclose = () => console.log("Disconnected");
  ```

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

  async def stream():
      uri = "wss://scrape.st/ws"
      headers = {"x-api-key": "YOUR_API_KEY"}
      async with websockets.connect(uri, extra_headers=headers) as ws:
          async for message in ws:
              event = json.loads(message)
              print(f"[{event['source']}] {event['mid']}")

  asyncio.run(stream())
  ```
</CodeGroup>

## Query Parameters

Append to the WebSocket URL:

| Parameter           | Type    | Default  | Description                                        |
| ------------------- | ------- | -------- | -------------------------------------------------- |
| `useFastX`          | boolean | `false`  | Receive `fast-x` push events (\~50ms latency)      |
| `ignoreFullPayload` | boolean | `false`  | Skip enriched `x` payloads, keep `fast-x` + others |
| `types`             | string  | `source` | Comma-separated event categories to receive        |

```
wss://scrape.st/ws?useFastX=true&ignoreFullPayload=true
```

### Event categories (`types`)

Every message carries a top-level `event` field naming its category. By default
a connection only receives `source` events — opt into derived events explicitly:

| `types` value    | Delivers                                                                                |
| ---------------- | --------------------------------------------------------------------------------------- |
| `source`         | Plain `SourceEvent`s from every tracked source (the default)                            |
| `profile_change` | [Profile changes](/rest-api-reference/profile/add-profile-watch) for watched X accounts |
| `ocr_result`     | [OCR text](/rest-api-reference/ocr) from images on tracked posts                        |

```
wss://scrape.st/ws?types=source,ocr_result,profile_change
```

Unknown values are ignored; an empty or missing `types` falls back to `source`.

## Event Flow

When `useFastX=true`, you receive **two events** per post:

1. **`fast-x`** — Arrives \~50ms after posting. Contains basic post data (text, author, timestamp) before GraphQL enrichment.
2. **`x`** — Arrives \~200-500ms after posting. Full `ResolvedXPost` with engagement counts, media, entities, quoted posts.

If you only need speed and don't care about engagement metrics, set `ignoreFullPayload=true` to skip the second event.

## Keepalive

The server pings idle connections every **30 seconds**. Connections with no activity for **60 seconds** are terminated.

To keep your connection alive, respond to pings (automatic in most WebSocket libraries) or send a manual ping:

```javascript theme={null}
// Manual keepalive
setInterval(() => ws.ping(), 25000);
```

## Reconnection

WebSocket does not auto-reconnect. Implement retry logic:

```javascript theme={null}
function connect() {
  const ws = new WebSocket("wss://scrape.st/ws", {
    headers: { "x-api-key": "YOUR_API_KEY" },
  });

  ws.on("close", () => {
    console.log("Reconnecting in 3s...");
    setTimeout(connect, 3000);
  });

  ws.on("message", (raw) => {
    const event = JSON.parse(raw.toString());
    // Process event
  });
}

connect();
```

## Payload Shape

Every message carries an `event` category and a stable `id`. A plain source
event looks like:

```json theme={null}
{
  "event": "source",
  "id": "x:1886493083207827456:44196397",
  "mid": "1886493083207827456",
  "sid": "44196397",
  "source": "x",
  "timestamp": 1712072400000,
  "payload": {
    "id": "1886493083207827456",
    "text": "Bitcoin hits new ATH! 🚀",
    "author": { "id": "44196397", "screen_name": "elonmusk", ... },
    ...
  }
}
```

See source pages for full payload details: [X](/sources/x#webhook-payload) · [Telegram](/sources/telegram#webhook-payload) · [Discord](/sources/discord#webhook-payload)
