Skip to main content

How it works

When you successfully log in, the server returns a resume object with:
  • serverEpoch: A token grouping your session’s cursors
  • replayChannels: Channels that support resume
  • resumeWindowMs: Max duration you can resume within
  • serverEntryIds: Latest known entryId per channel
You must persist the following:
  • serverEpoch from the last login_ok
  • The most recent entryId per channel received (lastSeenId)

Reconnect with cursors

When reconnecting, include serverEpoch and your latest lastSeenId per replayable channel:
{
  "type": "login",
  "apiKey": "YOUR_API_KEY",
  "serverEpoch": "SERVER_EPOCH_FROM_LAST_LOGIN_OK",
  "lastSeenId": {
    "fixtures": "1730079534820-21",
    "scores": "1730079534901-22"
  }
}

🐍 Python Resume Client with Simulated Disconnect

This example demonstrates:
  • Connecting with login
  • Simulating a dropped connection
  • Reconnecting and resuming using resume state
import asyncio
import json
import time
import websockets

# --- Configuration ---
WS_URL = "wss://your-websocket-url"
API_KEY = "your-api-key"

CHANNELS = ["fixtures", "scores"]
SIMULATE_DISCONNECT_AFTER_S = 5  # Disconnect after 5 seconds to trigger resume

# Resume state (must persist across reconnects)
server_epoch = None
replay_channels = set()
last_seen = {}  # channel -> entryId

# --- Resume Cursor Logic ---
def get_resume_cursors():
    if not last_seen or not replay_channels:
        return {}
    return {ch: eid for ch, eid in last_seen.items() if ch in replay_channels}

# --- WebSocket Connection ---
async def connect_once():
    global server_epoch, replay_channels, last_seen

    login = {
        "type": "login",
        "apiKey": API_KEY,
        "channels": CHANNELS
    }

    if server_epoch:
        login["serverEpoch"] = server_epoch
    cursors = get_resume_cursors()
    if cursors:
        login["lastSeenId"] = cursors

    print("[client] -> login:", login)

    async with websockets.connect(WS_URL) as ws:
        await ws.send(json.dumps(login))
        disconnect_at = time.monotonic() + SIMULATE_DISCONNECT_AFTER_S

        async for raw in ws:
            if time.monotonic() >= disconnect_at:
                print("[client] !! Simulating disconnect")
                await ws.close()
                return

            msg = json.loads(raw)

            # --- Resume Setup ---
            if msg.get("type") == "login_ok":
                resume = msg.get("resume", {})
                server_epoch = resume.get("serverEpoch")
                replay_channels = set(resume.get("replayChannels", []))
                print(f"[client] <- login_ok (epoch: {server_epoch})")
                continue

            if msg.get("type") == "resume_complete":
                print("[client] <- resume_complete")
                continue

            # --- Cursor Invalidation ---
            if msg.get("type") in ("snapshot_required", "resync_required"):
                for ch in msg.get("channels", []):
                    last_seen.pop(ch, None)
                print("[client] <-", msg)
                continue

            # --- Data Message ---
            ch = msg.get("channel")
            eid = msg.get("entryId")
            if ch and eid:
                last_seen[ch] = eid
                print("[client] <- data", ch, eid)

# --- Main Loop ---
async def main():
    while True:
        try:
            await connect_once()
        except Exception as e:
            print("[client] connection error:", repr(e))
        await asyncio.sleep(1)

if __name__ == "__main__":
    asyncio.run(main())
This client reconnects and resumes using the latest known entryId per channel and the serverEpoch from the last login_ok.

What to Expect

  1. The client connects and receives data.
  2. After 5 seconds, it simulates a disconnect.
  3. It automatically reconnects, resuming from the last received entryId.
  4. The server will respond with resume_complete if the resume was successful.
This demonstrates resumable subscriptions in real-time. For production use, persist serverEpoch and lastSeenId between process restarts.

💬 Ask an AI Assistant

Want to explore or ask questions about this page using your favorite AI? Click one of the links below — each one opens this page in the selected tool with a pre-filled prompt: