Streaming

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

Verbinden

Der Stream nimmt Ihren Schlüssel auf dieselben drei Arten wie REST entgegen. Von einem Server aus verwenden Sie einen Header; den URL-Parameter nur dort, wo sich keine Header setzen lassen. Kommt der Schlüssel nicht an oder wird er nicht akzeptiert, öffnet sich die Verbindung und schließt sofort mit 4001; der Schließgrund nennt die Ursache.

So senden Sie den SchlüsselVerwenden, wenn
Authorization: Bearer xsf_…Empfohlen für Server; derselbe Header wie bei REST
X-API-Key: xsf_…Gleichwertig zum obigen
?api_key=xsf_…Browser und Clients, die beim Handshake keine Header setzen können. Der Schlüssel wird Teil der URL, halten Sie solche URLs daher aus Logs heraus
python — Schlüssel im 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-ParameterBedeutung
api_keyIhr Schlüssel, wenn Sie den Header nicht setzen können
last_idNach dieser sid fortsetzen. Wird stillschweigend ignoriert, wenn der Schlüssel die Berechtigung replay nicht hat
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

Drei Frames stammen von uns statt von X:

FrameWann
system.helloErster Frame. targets ist die Zahl der Konten, die dieser Schlüssel in diesem Moment überwacht (später hinzugefügte Konten kommen ohne neue Verbindung dazu); replay_from gibt die last_id zurück, mit der Sie sich neu verbunden haben, und ist null, wenn Sie keine gesendet haben – dann wird nichts nachgeliefert
system.pingWird bei Stille als Keepalive gesendet, damit untätige Proxys den Socket nicht schließen
system.errorMit der Verbindung stimmt etwas nicht; ein Close folgt

Close-Code

Der Handshake gelingt immer, und der Close folgt unmittelbar, damit Ihre Client-Bibliothek Code und Grund sieht statt eines nackten HTTP-Fehlers.

Close-CodeBedeutung
1012Neustart des Dienstes während einer Wartung. Verbinden Sie sich mit Backoff neu und übergeben Sie die letzte verarbeitete sid als ?last_id=: Die Ereignisse der Lücke werden nachgeliefert, nichts geht verloren
4001Der Schlüssel wurde nicht akzeptiert. Der Schließgrund nennt die Ursache: missing_api_key (kein Schlüssel kam bei uns an: prüfen Sie den Header-Namen oder ob Ihr Proxy bzw. Ihre Client-Bibliothek ihn weiterreicht), invalid_api_key (kein solcher Schlüssel: Tippfehler oder ein durch Rotation ersetztes Secret), key_revoked (der Schlüssel wurde widerrufen)
4002Zu viele gleichzeitige Verbindungen für diesen Schlüssel; ein system.error-Frame geht dem Close voraus
4003Dem Schlüssel fehlt die Berechtigung stream

Richtig neu verbinden

Schreiben Sie das einmal richtig, nicht die naive Version. Die beiden Dinge, auf die es ankommt, sind exponentielles Backoff und das Persistieren von sid an einem Ort, der einen Prozessneustart überlebt.

python – produktionsreife Form
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)