Streaming

wss wss://ws.xsignalfeed.com/v1/stream

Connecting

The stream takes your key in the same three ways as REST. Use a header from a server; use the URL parameter only where headers cannot be set. If the key does not arrive or is not accepted, the connection opens and then closes at once with 4001, and the close reason says why.

How to send the keyUse it when
Authorization: Bearer xsf_…Recommended for servers; the same header as REST
X-API-Key: xsf_…Equivalent to the one above
?api_key=xsf_…Browsers, and clients that cannot set handshake headers. The key becomes part of the URL, so keep such URLs out of logs
python — key in a header
import websockets

async with websockets.connect("wss://ws.xsignalfeed.com/v1/stream",
                              additional_headers={"Authorization": "Bearer xsf_your_key"}) as ws:
    print(await ws.recv())    # {"t": "system.hello", ...}
Query parameterMeaning
api_keyYour key, when you cannot set the header
last_idResume after this sid. Silently ignored unless the key has the replay scope
import asyncio, json, websockets

async def main():
    url = "wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key"
    async with websockets.connect(url) as ws:
        async for raw in ws:
            ev = json.loads(raw)
            if ev["t"].startswith("system."): continue
            print(ev["t"], ev["target"]["screen_name"], ev["d"].get("text", "")[:60])

asyncio.run(main())

Frame

Three frames are ours rather than X's:

FrameWhen
system.helloFirst frame. targets is how many accounts this key watches at this moment (accounts added later join without a reconnect); replay_from echoes the last_id you reconnected with and is null when you sent none, in which case nothing is replayed
system.pingSent during silence as a keepalive, so idle proxies do not close the socket
system.errorSomething is wrong with the connection; a close follows

Close code

The handshake always succeeds and the close follows immediately, so your client library sees the code and the reason rather than a bare HTTP error.

Close codeMeaning
1012Service restart, during maintenance. Reconnect with backoff and pass the last sid you processed as ?last_id=: the events of the gap are replayed, nothing is lost
4001The key was not accepted. The close reason says why: missing_api_key (no key reached us: check the header name, or that your proxy or client library forwards it), invalid_api_key (no such key: a typo, or a secret replaced by a rotation), key_revoked (the key was revoked)
4002Too many concurrent connections for this key; a system.error frame precedes the close
4003The key lacks the stream scope

Reconnecting properly

Write this once, not the naive version. The two things that matter are exponential backoff and persisting sid somewhere that survives a process restart.

python — production shape
import asyncio, json, pathlib, random, websockets

STATE = pathlib.Path("xsf.cursor")          # survives restarts; a DB row is better still

async def run(key, handle):
    backoff = 1
    while True:
        last = STATE.read_text().strip() if STATE.exists() else None
        url = f"wss://ws.xsignalfeed.com/v1/stream?api_key={key}" + (f"&last_id={last}" if last else "")
        try:
            async with websockets.connect(url, ping_interval=20, max_size=2**22) as ws:
                backoff = 1
                async for raw in ws:
                    ev = json.loads(raw)
                    if ev["t"].startswith("system."): continue
                    handle(ev)                      # must be idempotent: dedupe on ev["id"]
                    if ev.get("sid"): STATE.write_text(ev["sid"])   # only after handling
        except Exception as e:
            print("disconnected:", e)
        await asyncio.sleep(backoff + random.random())   # jitter, or every client returns at once
        backoff = min(60, backoff * 2)