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

# WebSocket stream: connection rules and close codes

> How to connect to the real-time WebSocket stream, the one-connection-per-API-key rule, error and close codes, and how to reconnect safely.

The WebSocket stream delivers tweets for your [filter rules](https://docs.twitterapi.io/api-reference/endpoint/add_webhook_rule.md) and [monitored accounts](https://docs.twitterapi.io/api-reference/endpoint/add_user_to_monitor_tweet.md) in real time.

```
wss://ws.twitterapi.io/twitter/tweet/websocket
Header: x-api-key: YOUR_API_KEY
```

## One connection per API key

Each API key can have **one** WebSocket connection at a time.

* If a new connection opens with the same API key, **the new connection takes over**. The older connection receives a `replaced` event and is closed with code `4001`.
* Messages the old connection had already taken but not delivered are re-sent to the new connection.
* If you see `4001`, another process is using your key. **Do not reconnect the client that received `4001`**, or the two clients will keep kicking each other off.

## Errors before the connection opens

If the server refuses a connection, it answers the handshake with a normal HTTP error and a JSON body. Most WebSocket libraries expose this as a failed handshake with a status code.

| HTTP status | `error_code` | Meaning | What to do |
| - | - | - | - |
| 401 | `4003` | Missing `x-api-key` header | Send the header |
| 403 | `4003` | Invalid API key | Check the key in your dashboard |
| 429 | `4029` | Reconnecting too fast (one handshake per API key every 5 seconds) | Wait for `Retry-After` seconds, then connect again |
| 409 | `4009` | Another client keeps connecting with this API key | Stop the extra client. The client that is already connected keeps receiving data. New connections are refused for `Retry-After` seconds: 60 the first time, longer if it keeps happening (up to 10 minutes) |

Example body:

```json theme={null}
{
  "event_type": "error",
  "error_code": 4029,
  "message": "Reconnecting too fast. Wait retry_after_seconds before connecting again.",
  "retry_after_seconds": 5
}
```

## Events and close codes after the connection opens

| Event / close code | Meaning |
| - | - |
| `{"event_type": "connected"}` | Connection accepted |
| `{"event_type": "ping"}` | Keep-alive, sent about every 20 seconds. No reply needed |
| `{"event_type": "replaced", "error_code": 4001}`, then close `4001` | A newer connection with the same API key took over. Do not reconnect this client |

The server also sends WebSocket protocol pings. Standard libraries answer them automatically. A connection that stops answering (for example after a network drop) is closed after about 50 seconds.

## Reconnecting safely

* Close the old connection before you open a new one, and run only one client per API key.
* After a normal disconnect, wait at least 5 seconds before reconnecting, and back off if it keeps failing.
* On `4029`, wait `Retry-After` seconds.
* On `4001` or `4009`, stop. Another client is using the key.
* On `4003`, stop and fix the API key.

## Sample client (Python)

```python theme={null}
import asyncio, json
import websockets  # pip install websockets

URL = "wss://ws.twitterapi.io/twitter/tweet/websocket"
API_KEY = "YOUR_API_KEY"
STOP_CODES = {4001, 4003, 4009}

async def run():
    delay = 5
    while True:
        try:
            async with websockets.connect(URL, additional_headers={"x-api-key": API_KEY}) as ws:
                delay = 5
                async for raw in ws:
                    msg = json.loads(raw)
                    if msg.get("event_type") in ("connected", "ping"):
                        continue
                    if msg.get("event_type") == "replaced":
                        print("Replaced by a newer connection; stopping.")
                        continue
                    print(msg)  # handle tweet events here
        except websockets.exceptions.InvalidStatus as e:  # refused before opening
            try:
                body = json.loads(e.response.body or b"{}")
            except ValueError:  # not JSON, e.g. a proxy error page
                body = {}
            if body.get("error_code") in STOP_CODES:
                print("Stopping:", body.get("message"))
                return
            delay = int(e.response.headers.get("Retry-After", delay))
        except websockets.exceptions.ConnectionClosed as e:
            if e.rcvd and e.rcvd.code in STOP_CODES:
                print("Stopping: closed with", e.rcvd.code)
                return
        except OSError:
            pass  # network error
        await asyncio.sleep(delay)
        delay = min(delay * 2, 60)

asyncio.run(run())
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.