Skip to content

Catalogue WebSocket ​

Stream the same changes as Catalogue Events live over a WebSocket.

Download Available Items once, keep its last_update_at, then keep it current from the stream.

Connect ​

WSS wss://api.skinslink.com/ws/catalogue?games=csgo,rust

Authenticate with your merchant API key — the same one you use for the REST endpoints — in the X-Api-Key header of the WebSocket handshake. Narrow the games with the optional games query parameter; omit it for all.

ParameterInRequiredDescription
X-Api-Keyheader✅Your merchant API key
gamesquery—Comma-separated game codes (csgo, rust, dota2, tf2). Omit for all

The endpoint subscribes you on connect — you send nothing. On connect we send one of:

  • {"event":"auth_success"} — authenticated; catalogue_change frames start streaming
  • {"event":"auth_failed"} — the API key is missing or invalid; we then close the socket

What we send ​

Changes arrive in batches — about one per second, and only when something changed. After a slow period several batches may arrive back-to-back. Each batch arrives as:

json
{
  "event": "catalogue_change",
  "data": {
    "since": "2026-09-21T07:42:26.680856848Z",
    "next": "2026-09-21T07:42:27.060289140Z",
    "count": 2,
    "reset": false,
    "events": [
      {
        "type": "upsert",
        "at": "2026-09-21T07:42:26.9Z",
        "game": "csgo",
        "id": "38029384123",
        "item": {
          "id": "38029384123",
          "name": "AK-47 | Redline (Field-Tested)",
          "price": 12.45,
          "image_url": "https://steamcommunity-a.akamaihd.net/economy/image/class/730/..."
        }
      },
      { "type": "remove", "at": "2026-09-21T07:42:27.060289140Z", "game": "csgo", "id": "38029111999" }
    ]
  }
}

data is the same envelope as a Catalogue Events response — since, next, count, reset, events. Cards always carry the extended fields (class_id, instance_id, asset_id, bot_steam_id, float, paint_seed, inspect_url, phase) when we have them; exterior, rarity and rarity_color are not included. Apply the events as in Applying changes: upsert inserts or replaces the id, remove takes it out. Re-applying an event is safe.

Cursor & catch-up ​

Every batch carries since and next. Keep the next you last applied, and check each batch:

  • since equals your last next — apply the batch, then store its next.
  • since does not equal your last next — a batch was missed (a slow reader, a brief drop). Pull Catalogue Events from your last next until more is false, then resume applying the stream.

On (re)connect ​

The stream only carries what happens while you are connected. After connecting, first pull Catalogue Events from your stored next to cover the downtime, then apply live batches. Do the same after every reconnect.

Reset ​

A batch may carry reset: true with no events: download Available Items again and resume from the new last_update_at. A reset always arrives on its own.

Keep-alive & reconnect ​

We send a WebSocket Ping frame every 30 seconds; your client must answer with Pong within 60 seconds or we close the connection — most libraries do this automatically. There is no text heartbeat message. If the connection drops, reconnect with a growing backoff and catch up with a pull before trusting the live stream again. The connection negotiates permessage-deflate compression automatically when your client offers it.

Example (Python) ​

python
import json
import websocket  # pip install websocket-client

api_key = "your-merchant-api-key"
cursor = catalogue["last_update_at"]  # from your downloaded Available Items

def on_open(ws):
    catch_up(cursor)  # pull from the cursor to cover any downtime, then trust the stream

def on_message(ws, raw):
    global cursor
    msg = json.loads(raw)
    if msg["event"] != "catalogue_change":  # auth_success, ...
        return
    batch = msg["data"]

    if batch["reset"]:                       # download the catalogue again
        cursor = download_available_items()  # returns the new last_update_at
        return
    if batch["since"] != cursor:             # missed a batch -> pull the gap
        catch_up(cursor)
        return

    apply_events(batch["events"])            # upsert = insert/replace id, remove = delete id
    cursor = batch["next"]

def on_close(ws, status, message):
    reconnect_with_backoff()

ws = websocket.WebSocketApp(
    "wss://api.skinslink.com/ws/catalogue?games=csgo,rust",
    header={"X-Api-Key": api_key},
    on_open=on_open,
    on_message=on_message,
    on_close=on_close,
)
ws.run_forever()  # answers server pings with pongs automatically