Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Real-time WebSocket feed for crypto exchange listing announcements.

What is this?

This service delivers instant notifications when a cryptocurrency exchange publishes an announcement – listings and other exchange announcements. You connect via WebSocket and receive structured JSON messages in real time.

Currently supported exchanges:

ExchangeStatus
BinanceLive
BithumbLive
UpbitLive
More coming soon

Supported announcement types:

TypeDescription
spot_listingNew spot market listing
futures_listingNew futures/perpetual listing
spot_delistingSpot market delisting (coming soon)
futures_delistingFutures/perpetual delisting (coming soon)
not_listingOther exchange announcement (maintenance, airdrop, token swap, etc.)

How it works

Exchange publishes announcement
        |
        v
   Detection engine (< 5ms)
        |
        v
   Dispatch server (< 1ms)
        |
        v
   Your WebSocket client
  1. Our detection engine monitors exchange announcement pages continuously.
  2. When a new listing is detected, it is parsed, classified, and forwarded to the dispatch server.
  3. The dispatch server broadcasts the announcement to all connected WebSocket subscribers simultaneously.
  4. You receive a JSON message with the ticker, exchange, listing type, and precise timestamps.

Key features

  • Ultra-low latency – sub-millisecond dispatch from detection to your connection.
  • Precise timestamps – microsecond-resolution timestamps at every stage (publish, detect, dispatch) so you can measure your exact latency.
  • Exchange filtering – subscribe only to the exchanges you care about.
  • Always-on heartbeat – know immediately if your connection drops.
  • Zero-copy broadcast – announcements are serialized once and shared across all subscribers for maximum throughput.
TopicDescription
Quick StartConnect in under 5 minutes
AuthenticationAPI key format and usage
WebSocket APIConnection details and parameters
Message ReferenceFull JSON schema for all messages
Exchange FilteringSubscribe to specific exchanges
Rate Limits & SecurityLimits, TLS, and security details
Error Handling & ReconnectionClose codes and retry strategy
Code ExamplesPython, Node.js, Go examples

Quick Start

Get connected and receiving announcements in under 5 minutes.

1. Get your API key

You will receive an API key from the administrator. It looks like this:

dsk_a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890ab

All keys start with the dsk_ prefix followed by 64 hexadecimal characters.

Store your API key securely. It is shown only once when created and cannot be retrieved later.

2. Connect to the WebSocket

wss://cryptolisting.ws

Pass your API key via the X-API-Key HTTP header during the WebSocket handshake.

pip install websocket-client
# pip install websocket-client
import json, websocket
from datetime import datetime

URL    = "wss://cryptolisting.ws"
HEADER = ["X-API-Key: dsk_your_key_here"]

def on_open(ws):
    print("Connected!")

def on_message(ws, message):
    data = json.loads(message)
    ts   = datetime.now().strftime("%H:%M:%S.%f")[:-3]

    if data["type"] == "announcement":
        print(f"[{ts}] {data['listingType']} | {data['ticker']} on {data['publisher']}")
        print(f"        {data['title']}")
    elif data["type"] == "welcome":
        print(f"[{ts}] Welcome — tier={data['tier']}, cex={data['allowedCex']}")

def on_close(ws, code, msg):
    print(f"Disconnected ({code}). Reconnecting...")
    connect()

def connect():
    websocket.WebSocketApp(
        URL, header=HEADER,
        on_open=on_open, on_message=on_message, on_close=on_close
    ).run_forever(ping_interval=30, ping_timeout=10)

connect()

3. Receive messages

Once connected, you will immediately receive a welcome message confirming your subscription:

{
  "type": "welcome",
  "tier": "premium",
  "maxConnections": 5,
  "allowedCex": "*",
  "expiresInSecs": 86400
}

Then you will receive:

  • Heartbeats every 30 seconds – confirms the connection is alive.
  • Announcements whenever a listing or other exchange announcement is detected.
{
  "type": "announcement",
  "title": "Binance Will List TOKEN (TOKEN)",
  "ticker": "TOKEN",
  "publisher": "binance",
  "listingType": "spot_listing",
  "publishTimestampUs": 1710345000000000,
  "detectedTimestampUs": 1710345000005000,
  "dispatchTimestampUs": 1710345000006000
}

4. Filter by exchange (optional)

To receive announcements only from specific exchanges, add the cex query parameter:

wss://cryptolisting.ws?cex=binance
wss://cryptolisting.ws?cex=binance,upbit

Omit the parameter to receive announcements from all exchanges.

Next steps

Authentication

Every WebSocket connection must be authenticated with a valid API key.

API key format

dsk_<64 hex characters>

Example:

dsk_a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890ab
  • Prefix: dsk_ (always)
  • Body: 64 hexadecimal characters (32 random bytes)

Passing your API key

Set the X-API-Key header during the WebSocket handshake:

X-API-Key: dsk_your_key_here

This is the recommended method. The key stays out of URLs and server logs.

Method 2: Query parameter

Append the key as a query parameter:

wss://cryptolisting.ws?api_key=dsk_your_key_here

Not recommended. Query parameters may appear in server access logs, proxy logs, and browser history. Use the header method whenever possible.

Key properties

Each API key has the following properties, configured by the administrator:

PropertyDescription
TierSubscription tier: basic, premium, or enterprise
Max connectionsMaximum simultaneous WebSocket connections allowed with this key
Allowed CEXExchanges this key can subscribe to ("*" = all)
ExpirationOptional expiration date – key becomes invalid after this time

Key lifecycle

  1. Created – The administrator generates a key. The full key is shown exactly once.
  2. Active – The key can be used to connect. Connections are subject to the key’s limits.
  3. Expired – If the key has an expiration date and that date has passed, connections are refused and active sessions are disconnected with close code 1000 and reason key_expired.
  4. Revoked – The administrator revokes the key. All active sessions are immediately disconnected with close code 1000 and reason key_revoked. The key can no longer be used.

Security

  • Keys are hashed with SHA-256 before storage. Even if the database is compromised, your raw key cannot be recovered.
  • Keys are transmitted over TLS-encrypted WebSocket connections (WSS).
  • Invalid keys are cached server-side for 30 seconds to prevent brute-force attempts.

WebSocket API

Connection endpoint

wss://cryptolisting.ws

The server uses binary WebSocket frames containing UTF-8 JSON.

Query parameters

ParameterRequiredDescriptionExample
cexNoComma-separated list of exchanges to subscribe tobinance,upbit

Connection lifecycle

Client                              Server
  |                                   |
  |--- WSS handshake + API key -----→ |
  |                                   |-- Validate key
  |                                   |-- Check rate limits
  |                                   |-- Check connection limits
  |←---- 101 Switching Protocols -----|
  |                                   |
  |←---- Welcome message -------------|  (immediate)
  |                                   |
  |←---- Heartbeat -------------------|  (every 30s)
  |←---- Heartbeat -------------------|
  |                                   |
  |←---- Announcement ----------------|  (when detected)
  |                                   |
  |--- {"type":"test"} -------------→ |  (optional)
  |←---- Test Announcement -----------|  (only to you)
  |                                   |
  |←---- PING ----------------------- |  (every 15s)
  |--- PONG ------------------------→ |
  |                                   |
  |←---- Close (1000, key_expired) ---|  (if key expires)
  |                                   |

1. Handshake

The server validates your API key during the HTTP upgrade handshake. If validation fails, you receive an HTTP error response (not a WebSocket frame):

HTTP CodeReason
400Malformed WebSocket upgrade request
401Missing API key
403Invalid, revoked, or expired API key
429Rate limit or connection limit exceeded

2. Welcome message

Immediately after a successful handshake, the server sends a welcome message confirming your subscription parameters. See Message Reference.

3. Heartbeats

The server sends a JSON heartbeat message every 30 seconds. If you do not receive a heartbeat within ~35 seconds, your connection may be dead.

4. Ping/Pong

In addition to JSON heartbeats, the server sends WebSocket PING frames every 15 seconds. Your WebSocket library handles PONG responses automatically. If the server receives no PONG within 30 seconds, it closes your connection.

Most WebSocket libraries (Python websockets, Node.js ws, Go gorilla/websocket) handle PING/PONG automatically. You do not need to implement this yourself.

5. Announcements

When a listing or other exchange announcement is detected, the server sends an announcement message to all subscribers whose exchange filter matches. See Message Reference.

6. Disconnection

The server may close your connection with a WebSocket close frame. The close code and reason indicate why. See Error Handling.

Client-to-server messages

Client messages are subject to rate limiting (3 messages/minute) and frame size limits (1 KB max). The only supported client message is the test request:

{"type":"test"}

This returns a fake announcement to verify your integration is working. See Message Reference — Test Announcement for details.

Exceeding the client message rate limit (3/min) will result in disconnection. Test requests are additionally limited to 1 per minute per API key.

Message Reference

All messages are delivered as binary WebSocket frames containing UTF-8 JSON. Every message has a type field.

Welcome

Sent once, immediately after a successful connection.

{
  "type": "welcome",
  "tier": "premium",
  "maxConnections": 5,
  "allowedCex": "*",
  "expiresInSecs": 2592000
}
FieldTypeDescription
typestringAlways "welcome"
tierstringYour subscription tier: basic, premium, or enterprise
maxConnectionsintegerMaximum simultaneous connections allowed with your key
allowedCexstringExchanges you will receive announcements from (effective filter after applying key restrictions and your cex preference). "*" means all exchanges
expiresInSecsinteger|nullSeconds until your key expires, or null if no expiration

Announcement

Sent whenever a listing or other exchange announcement is detected on a monitored exchange.

Single ticker:

{
  "type": "announcement",
  "title": "Binance Will List TOKEN (TOKEN)",
  "ticker": "TOKEN",
  "publisher": "binance",
  "listingType": "spot_listing",
  "publishTimestampUs": 1710345000000000,
  "detectedTimestampUs": 1710345000005000,
  "dispatchTimestampUs": 1710345000006000
}

Multiple tickers (one announcement listing several assets at once):

{
  "type": "announcement",
  "title": "Binance Will List ABC, DEF and GHI",
  "ticker": "ABC,DEF,GHI",
  "publisher": "binance",
  "listingType": "spot_listing",
  "publishTimestampUs": 1710345000000000,
  "detectedTimestampUs": 1710345000005000,
  "dispatchTimestampUs": 1710345000006000
}
FieldTypeDescription
typestringAlways "announcement"
titlestringOriginal announcement title from the exchange
tickerstringAsset symbol(s). Comma-separated when multiple tickers are listed in the same announcement (e.g. "BTC" or "ABC,DEF,GHI")
publisherstringExchange name in lowercase (e.g. "binance", "upbit", "bithumb")
listingTypestringOne of the listing types below
publishTimestampUsintegerWhen the exchange published the announcement (microseconds since UNIX epoch)
detectedTimestampUsintegerWhen our detection engine captured it (microseconds since UNIX epoch)
dispatchTimestampUsintegerWhen the dispatch server broadcasted it (microseconds since UNIX epoch)

Always handle ticker as a potentially comma-separated list. Some exchange announcements list multiple assets at once. Split on , to get individual tickers:

tickers = data["ticker"].split(",")  # ["ABC", "DEF", "GHI"]

Supported exchanges (publisher)

ValueExchange
binanceBinance
upbitUpbit
bithumbBithumb

More exchanges coming soon. This list will grow over time. Use the cex query parameter when connecting to filter which exchanges you receive.

Listing types

ValueMeaning
spot_listingNew spot market listing
futures_listingNew futures/perpetual listing
spot_delistingSpot market delisting (coming soon)
futures_delistingFutures/perpetual delisting (coming soon)
not_listingOther exchange announcement (maintenance, airdrop, token swap, etc.)

Delistings coming soon. The spot_delisting and futures_delisting types are reserved for future use. Currently, announcements that are not listings are sent with listingType: "not_listing".

Measuring your latency

All timestamps are in microseconds since UNIX epoch (not milliseconds, not nanoseconds).

Detection delay    = detectedTimestampUs - publishTimestampUs
Dispatch delay     = dispatchTimestampUs - detectedTimestampUs
Your network delay = your_receive_time_us - dispatchTimestampUs
Total end-to-end   = your_receive_time_us - publishTimestampUs

Python example:

import time

def on_announcement(msg):
    now_us = int(time.time() * 1_000_000)
    detection_ms = (msg["detectedTimestampUs"] - msg["publishTimestampUs"]) / 1000
    dispatch_ms  = (msg["dispatchTimestampUs"] - msg["detectedTimestampUs"]) / 1000
    network_ms   = (now_us - msg["dispatchTimestampUs"]) / 1000
    total_ms     = (now_us - msg["publishTimestampUs"]) / 1000

    print(f"Detection: {detection_ms:.2f}ms")
    print(f"Dispatch:  {dispatch_ms:.2f}ms")
    print(f"Network:   {network_ms:.2f}ms")
    print(f"Total:     {total_ms:.2f}ms")

Test Announcement

You can request a fake announcement to verify your integration. Send a JSON message to the server:

{"type":"test"}

The server replies with a test_announcement — identical to a real announcement except for the type field:

{
  "type": "test_announcement",
  "title": "Binance Will List MOCKX (MOCKX)",
  "ticker": "MOCKX",
  "publisher": "binance",
  "listingType": "spot_listing",
  "publishTimestampUs": 1743850000000000,
  "detectedTimestampUs": 1743850001999800,
  "dispatchTimestampUs": 1743850002000000
}

The fields are the same as a regular Announcement. The timestamps are simulated: publishTimestampUs is 1 second before dispatch, and detectedTimestampUs is 200µs before dispatch.

This message is only sent to you, not broadcast to other subscribers. The exchange, listing type, ticker, and title are randomized from realistic templates.

Test rate limit

Test requests are rate-limited to 1 per minute per API key (shared across all connections using the same key). If you exceed the limit, the server responds with an error instead of a test announcement:

{
  "type": "error",
  "code": "test_rate_limited",
  "retryAfterSecs": 42
}
FieldTypeDescription
typestringAlways "error"
codestringError code: "test_rate_limited"
retryAfterSecsintegerSeconds to wait before retrying

Heartbeat

Sent every 30 seconds to indicate the server is alive.

{
  "type": "heartbeat",
  "timestampNs": 1710345030000000000,
  "timeUtc": "2024-03-13T10:30:30.000000Z"
}
FieldTypeDescription
typestringAlways "heartbeat"
timestampNsintegerCurrent server time in nanoseconds since UNIX epoch
timeUtcstringCurrent server time in ISO 8601 UTC format

If you do not receive a heartbeat within 35 seconds, your connection is likely dead. Initiate a reconnection.

Exchange Filtering

You can choose to receive announcements from specific exchanges only, rather than receiving everything.

How filtering works

Filtering is applied at two levels:

1. Key-level restriction (set by administrator)

When your API key is created, the administrator can restrict it to specific exchanges. For example, a key may be limited to binance,upbit only.

You can check your key’s allowed exchanges in the welcome message you receive upon connection:

{
  "type": "welcome",
  "allowedCex": "binance,upbit",
  ...
}

A value of "*" means your key has access to all exchanges.

2. Client-side preference (set by you)

When connecting, you can further narrow your subscription using the cex query parameter:

wss://cryptolisting.ws?cex=binance

Multiple exchanges:

wss://cryptolisting.ws?cex=binance,upbit

All exchanges (default):

wss://cryptolisting.ws

Effective filter

The effective filter is the intersection of your key’s allowed exchanges and your client-side preference:

Key allowsYou requestYou receive
* (all)binancebinance
* (all)binance,upbitbinance,upbit
* (all)(nothing)All exchanges
binance,upbitbinancebinance
binance,upbitupbitupbit
binance,upbit(nothing)binance,upbit
binanceupbit(nothing – no match)

If your key restricts you to binance and you request upbit, you will connect successfully but receive no announcements (heartbeats are still sent).

Heartbeats are always delivered

Heartbeat messages are always sent regardless of your exchange filter. They are not tied to any specific exchange.

Rate Limits & Security

Connection limits

LimitValueScope
Per-IP concurrent connections20Single IP address
Per-IP connection rate10 / minuteSliding window
Per-key concurrent connectionsConfigurablePer API key

If you exceed any of these limits, the server responds with HTTP 429 Too Many Requests during the handshake:

{
  "error": "max_connections_reached",
  "limit": 5,
  "current": 5
}

Client message limits

LimitValueScopeConsequence
Message rate3 / minutePer connectionConnection closed (rate_limit_exceeded)
Max frame size1 KBPer frameConnection closed (frame_too_large)
Test requests1 / minutePer API keyError response (test_rate_limited)

The test request rate limit is shared across all connections using the same API key. Unlike the other limits, exceeding it does not disconnect you — you receive an error message with a retryAfterSecs field instead. See Message Reference — Test Announcement.

TLS

All connections use TLS encryption by default (WSS on port 9201).

The server may use a self-signed certificate. If so, you will need to disable certificate verification in your client. See Code Examples for how to do this in each language.

API key security

  • Keys are hashed before storage – raw keys cannot be recovered from the database.
  • Keys can be revoked instantly by an administrator, immediately disconnecting all active sessions.
  • Keys can have expiration dates – expired keys are automatically rejected.

Error Handling & Reconnection

HTTP errors (during handshake)

If authentication or rate limiting fails, the server rejects the WebSocket upgrade with an HTTP error:

HTTP CodeReasonAction
400Malformed requestFix your WebSocket client
401Missing API keyAdd X-API-Key header
403Invalid, revoked, or expired keyContact administrator for a new key
429Rate limit or connection limit exceededWait and retry with backoff

WebSocket close codes

Once connected, the server may close your connection with a close frame. The reason field tells you why:

CodeReasonMeaningShould reconnect?
1000key_expiredYour API key’s expiration date has passedNo – get a new key
1000key_revokedAdministrator revoked your keyNo – get a new key
1008too_slowYou fell 10+ messages behind (lagging consumer)Yes
1008rate_limit_exceededYou sent too many messages to the server (>3/min)Yes – stop sending messages
1009frame_too_largeYou sent a frame larger than 1 KBYes – stop sending large frames

Exponential backoff

Attempt 1: wait 1 second
Attempt 2: wait 2 seconds
Attempt 3: wait 4 seconds
Attempt 4: wait 8 seconds
...
Cap: 300 seconds (5 minutes)

Decision logic

On disconnect:
  |
  |-- Close reason = "key_expired" or "key_revoked"?
  |     → Stop. Your key is no longer valid.
  |
  |-- Close reason = "rate_limit_exceeded"?
  |     → Wait 60s, then reconnect. Stop sending messages.
  |
  |-- Close reason = "too_slow"?
  |     → Reconnect immediately. Process messages faster.
  |
  |-- HTTP 429?
  |     → Back off. You're connecting too frequently.
  |
  |-- Unexpected disconnect / network error?
        → Reconnect with exponential backoff.

Heartbeat-based health check

Monitor heartbeats to detect silent disconnections:

import asyncio

HEARTBEAT_TIMEOUT = 35  # seconds

async def monitor_connection(ws):
    while True:
        try:
            msg = await asyncio.wait_for(ws.recv(), timeout=HEARTBEAT_TIMEOUT)
            data = json.loads(msg)
            if data["type"] == "announcement":
                handle_announcement(data)
        except asyncio.TimeoutError:
            print("No heartbeat received -- reconnecting")
            break  # Exit loop and reconnect

The server sends heartbeats every 30 seconds. If you receive nothing for 35 seconds, assume the connection is dead and reconnect.

Code Examples

Production-ready client implementations with reconnection, heartbeat monitoring, and latency tracking.

Requires: pip install websockets

import asyncio
import json
import ssl
import time
import websockets

API_KEY = "dsk_your_key_here"
WS_URL  = "wss://cryptolisting.ws"
# Filter specific exchanges (optional):
# WS_URL = "wss://cryptolisting.ws?cex=binance,upbit"

HEARTBEAT_TIMEOUT = 35  # seconds
MAX_RETRIES = 20


def on_announcement(msg: dict):
    now_us = int(time.time() * 1_000_000)
    network_ms = (now_us - msg["dispatchTimestampUs"]) / 1000
    total_ms   = (now_us - msg["publishTimestampUs"]) / 1000

    print(f"[{msg['listingType']}] {msg['ticker']} on {msg['publisher']}")
    print(f"  Title:   {msg['title']}")
    print(f"  Latency: {total_ms:.2f}ms total, {network_ms:.2f}ms network")


async def connect():
    ssl_ctx = ssl.create_default_context()
    ssl_ctx.check_hostname = False
    ssl_ctx.verify_mode = ssl.CERT_NONE

    headers = {"X-API-Key": API_KEY}

    for attempt in range(MAX_RETRIES):
        try:
            async with websockets.connect(
                WS_URL, extra_headers=headers, ssl=ssl_ctx
            ) as ws:
                print("Connected!")

                while True:
                    try:
                        raw = await asyncio.wait_for(
                            ws.recv(), timeout=HEARTBEAT_TIMEOUT
                        )
                    except asyncio.TimeoutError:
                        print("No heartbeat -- reconnecting")
                        break

                    msg = json.loads(raw)

                    if msg["type"] == "welcome":
                        print(f"Welcome: tier={msg['tier']}, "
                              f"cex={msg['allowedCex']}")
                        # Request a test announcement to verify integration
                        await ws.send(json.dumps({"type": "test"}))

                    elif msg["type"] in ("announcement", "test_announcement"):
                        prefix = "[TEST] " if msg["type"] == "test_announcement" else ""
                        print(f"{prefix}", end="")
                        on_announcement(msg)

                    elif msg["type"] == "heartbeat":
                        pass  # Connection alive

        except websockets.ConnectionClosed as e:
            if e.rcvd:
                reason = e.rcvd.reason
                if reason in ("key_expired", "key_revoked"):
                    print(f"Key is no longer valid: {reason}")
                    return
                print(f"Disconnected: {reason}")

        except Exception as e:
            print(f"Connection error: {e}")

        backoff = min(2 ** attempt, 300)
        print(f"Reconnecting in {backoff}s (attempt {attempt + 1})")
        await asyncio.sleep(backoff)

    print("Max retries exceeded")


if __name__ == "__main__":
    asyncio.run(connect())