Streaming
wss wss://ws.xsignalfeed.com/v1/stream
Verbinden
De stream neemt uw sleutel op dezelfde drie manieren aan als REST. Gebruik vanaf een server een header; gebruik de URL-parameter alleen waar geen headers te zetten zijn. Komt de sleutel niet aan of wordt hij niet geaccepteerd, dan gaat de verbinding open en direct weer dicht met 4001, en de sluitreden zegt waarom.
| Zo stuurt u de sleutel | Gebruik het als |
|---|---|
Authorization: Bearer xsf_… | Aanbevolen voor servers; dezelfde header als bij REST |
X-API-Key: xsf_… | Gelijkwaardig aan de vorige |
?api_key=xsf_… | Browsers, en clients die bij de handshake geen headers kunnen zetten. De sleutel wordt deel van de URL, dus houd zulke URL's uit logs |
python — sleutel in een 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", ...}
| Queryparameter | Betekenis |
|---|---|
| api_key | Uw sleutel, wanneer u de header niet kunt zetten |
| last_id | Hervat na deze sid. Wordt stilzwijgend genegeerd tenzij de sleutel de replay-scope heeft |
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())
import WebSocket from "ws"; const ws = new WebSocket("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key"); ws.on("message", (raw) => { const ev = JSON.parse(raw); if (ev.t.startsWith("system.")) return; console.log(ev.t, ev.target.screen_name, (ev.d.text ?? "").slice(0, 60)); });
c, _, err := websocket.DefaultDialer.Dial("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key", nil) for { _, raw, err := c.ReadMessage() if err != nil { break } var ev Event json.Unmarshal(raw, &ev) if strings.HasPrefix(ev.T, "system.") { continue } log.Println(ev.T, ev.Target.ScreenName) }
Frame
Drie frames komen van ons in plaats van van X:
| Frame | Wanneer |
|---|---|
system.hello | Eerste frame. targets is het aantal accounts dat deze sleutel op dit moment volgt (later toegevoegde accounts doen mee zonder opnieuw te verbinden); replay_from geeft de last_id terug waarmee je opnieuw verbond en is null als je er geen stuurde, en dan wordt er niets opnieuw afgespeeld |
system.ping | Verzonden tijdens stilte als keepalive, zodat inactieve proxy's de socket niet sluiten |
system.error | Er is iets mis met de verbinding; een sluiting volgt |
Sluitcode
De handshake slaagt altijd en de sluiting volgt onmiddellijk, zodat uw clientbibliotheek de code en de reden ziet in plaats van een kale HTTP-fout.
| Sluitcode | Betekenis |
|---|---|
1012 | Herstart van de dienst tijdens onderhoud. Verbind opnieuw met backoff en geef de laatste verwerkte sid mee als ?last_id=: de events van de onderbreking worden opnieuw afgespeeld, er gaat niets verloren |
4001 | De sleutel werd niet geaccepteerd. De sluitreden zegt waarom: missing_api_key (er kwam geen sleutel bij ons aan: controleer de headernaam, of dat uw proxy of clientbibliotheek hem doorgeeft), invalid_api_key (zo'n sleutel bestaat niet: een typfout, of een geheim dat door een rotatie is vervangen), key_revoked (de sleutel is ingetrokken) |
4002 | Te veel gelijktijdige verbindingen voor deze sleutel; een system.error-frame gaat aan de sluiting vooraf |
4003 | De sleutel mist de stream-scope |
Correct opnieuw verbinden
Schrijf dit één keer, en niet de naïeve versie. De twee dingen die ertoe doen zijn exponentiële backoff en het bewaren van sid op een plek die een procesherstart overleeft.
python — productievorm
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)