Exchange Filtering
Two filter layers narrow the stream you receive. The effective scope is their intersection.
1. Key restriction
Set by the administrator at key creation. Your effective allow-list appears in the welcome message:
{ "type": "welcome", "allowedCex": "binance,upbit", ... }
"*" means all exchanges.
2. ?cex= query parameter
Pass on the WebSocket URL:
| URL | Receives |
|---|---|
wss://cryptolisting.ws | All exchanges |
wss://cryptolisting.ws?cex=binance | Binance only |
wss://cryptolisting.ws?cex=binance,upbit | Binance + Upbit |
wss://cryptolisting.ws?cex=upbit | Upbit only |
wss://kr.cryptolisting.ws?cex=upbit | Upbit only, from the Seoul endpoint |
Upbit only
Two ways to receive nothing but Upbit:
wss://cryptolisting.ws?cex=upbit # Tokyo endpoint, Upbit-filtered
wss://kr.cryptolisting.ws?cex=upbit # Seoul endpoint — Upbit is all it carries
The Seoul endpoint only ever carries Upbit, so ?cex=upbit there is a no-op you can keep for clarity. Prefer Seoul if your bot runs in Korea, Tokyo if it also needs Binance or Bithumb — see WebSocket API.
Upbit titles are Korean. The schema is identical to every other publisher — only title changes language:
{
"type": "announcement",
"title": "플루언트(BLEND) 신규 거래지원 안내 (KRW, BTC, USDT 마켓)",
"ticker": "BLEND",
"publisher": "upbit",
"listingType": "spot_listing",
"detectedTimestampUs": 1710345000005000,
"dispatchTimestampUs": 1710345000006000,
"abnormalDetectionLatency": false,
"markets": "KRW,BTC,USDT"
}
ticker is pre-extracted, so you never have to parse Korean yourself. Branch on listingType and publisher, not on title:
if msg["publisher"] == "upbit" and msg["listingType"] == "spot_listing":
for ticker in msg["ticker"].split(","): # already extracted from the Korean title
snipe(ticker)
The split(",") is not defensive padding. Upbit routinely opens several assets in a single
notice, and that notice arrives as one event listing every symbol — see
ticker. Passing msg["ticker"] straight to an order
would send "CYS,ICNT,XAN,EDEN,AIOZ,ALLO" as a symbol.
Effective filter
| Key allows | You request | You receive |
|---|---|---|
* | binance | Binance |
* | none | All |
binance,upbit | binance | Binance |
binance,upbit | upbit | Upbit |
binance,upbit | none | Binance + Upbit |
binance | upbit | nothing (no overlap) |
If the intersection is empty, the connection still succeeds and stays alive (PING and heartbeat keep flowing) but you receive no announcement messages. Verify your allowedCex against your ?cex= value.
Filtering by event type
?cex= narrows by exchange. To narrow by event class, branch on listingType in your handler:
def on_message(ws, raw):
msg = json.loads(raw)
if msg["type"] != "announcement":
return
lt = msg["listingType"]
if lt == "spot_listing":
snipe(msg["publisher"], msg["ticker"])
elif lt in ("spot_delisting", "futures_delisting"):
unwind(msg["publisher"], msg["ticker"])
elif lt in ("monitoring_tag_extend", "monitoring_tag_remove", "caution_released"):
log_risk_signal(msg["publisher"], msg["ticker"], lt)
The schema is identical across exchanges, so a single match / switch / if-elif chain covers every type. See Listing types for the full list.