PKCryptolisting
WebSocket
Руководство по подключению
Получайте объявления Upbit, Bithumb, Binance и Bybit в реальном времени всего за несколько строк кода.
Получите API-ключ через Telegram-бота и подключитесь, следуя инструкции ниже.
ENDPOINT · KR
wss://kr.pkcryptolisting.com
Upbit · Bithumb
ENDPOINT · TOKYO
wss://tokyo.pkcryptolisting.com
Upbit · Bithumb · Binance · Bybit
PROTOCOL
WebSocket Secure (WSS)
AUTH
API Key (Выдача через Telegram)
SERVER LOCATION
AWS Seoul (ap-northeast-2) / AWS Tokyo (ap-northeast-1)
1. Подключение и аутентификация
После установления WebSocket-соединения необходимо немедленно отправить сообщение аутентификации. Если аутентификация не завершена в течение 5 секунд, соединение закрывается автоматическисоединение автоматически завершится
Чтобы получать уведомления только с определённой биржи, добавьте
Чтобы получать уведомления только с определённой биржи, добавьте
exchange поле.
⚠️ Примечания
•
•
•
wss://kr.pkcryptolisting.com(Korea) эндпоинт поддерживает только
UPBIT ·
BITHUMB.•
wss://tokyo.pkcryptolisting.com(Tokyo) эндпоинт поддерживает все биржи, включая
UPBIT ·
BITHUMB ·
BINANCE ·
BYBIT .
pip install websockets
npm install ws
go get github.com/gorilla/websocket
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = { version = "0.21", features = ["native-tls"] }
futures-util = "0.3"
serde_json = "1"
url = "2"import asyncio, json, websockets async def connect(): async with websockets.connect("wss://kr.pkcryptolisting.com") as ws: # or wss://tokyo.pkcryptolisting.com for all 4 exchanges # send auth message await ws.send(json.dumps({ "type": "auth", "key": "YOUR_API_KEY", # issued via Telegram bot # "exchange": "Upbit" # optional: "Upbit" | "Bithumb" (omit for all) })) asyncio.run(connect())
// npm install ws const WebSocket = require("ws"); const ws = new WebSocket("wss://kr.pkcryptolisting.com"); // or wss://tokyo.pkcryptolisting.com for all 4 exchanges ws.on("open", () => { // send auth message ws.send(JSON.stringify({ type: "auth", key: "YOUR_API_KEY", // issued via Telegram bot // exchange: "Upbit" // optional: "Upbit" | "Bithumb" (omit for all) })); });
// go get github.com/gorilla/websocket conn, _, _ := websocket.DefaultDialer.Dial("wss://kr.pkcryptolisting.com", nil)// or wss://tokyo.pkcryptolisting.com for all 4 exchanges // send auth message auth, _ := json.Marshal(map[string]string{"type": "auth", "key": "YOUR_API_KEY"}) // "exchange": "Upbit" // optional: "Upbit" | "Bithumb" (omit for all) conn.WriteMessage(websocket.TextMessage, auth)
let url = url::Url::parse("wss://kr.pkcryptolisting.com").unwrap();// or wss://tokyo.pkcryptolisting.com for all 4 exchanges let (mut ws, _) = connect_async(url).await.unwrap(); // send auth message let auth = serde_json::json!({"type": "auth", "key": "YOUR_API_KEY" // "exchange": "Upbit" // optional: "Upbit" | "Bithumb" (omit for all) }).to_string(); ws.send(Message::Text(auth)).await.ok();
2. Ответ на аутентификацию
В случае успеха возвращаются данные о тарифе, задержке и сроке действия.
✓ Успешно (auth_ok)
{ "type": "auth_ok", "plan": "premium", "delay_ms": 0, "expires_at": "2026-06-30", "message": "Connected..." }
✕ Ошибка (error)
{ "type": "error", "code": 4004, "message": "Invalid key" }
3. Получение сообщений
После аутентификации новые объявления будут поступать автоматически.
async for автоматически прервёт цикл при завершении соединения.
# message loop — exits cleanly on disconnect async for raw in ws: msg = json.loads(raw) msg_type = msg.get("type") if msg_type == "auth_ok": print(f"connected plan={msg['plan']} delay={msg['delay_ms']}ms") elif msg_type == "error": print(f"error {msg['code']}: {msg['message']}") break elif msg_type == "plan_updated": # plan changed print(f"plan updated: {msg['plan']} delay={msg['delay_ms']}ms") else: # announcement print(f"[{msg['exchange']}] {msg['title']}") print(f" symbol={msg['symbol']} detected={msg['detectedTime']} {msg.get('timezone', '')}")
// message loop — exits cleanly on disconnect ws.on("message", (raw) => { const msg = JSON.parse(raw); if (msg.type === "auth_ok") console.log(`connected plan=${msg.plan} delay=${msg.delay_ms}ms`); else if (msg.type === "error") { console.log(`error ${msg.code}: ${msg.message}`); ws.close(); } else if (msg.type === "plan_updated") // plan changed console.log(`plan updated: ${msg.plan} delay=${msg.delay_ms}ms`); else { // announcement console.log(`[${msg.exchange}] ${msg.title}`); console.log(` symbol=${msg.symbol} detected=${msg.detectedTime} ${msg.timezone || ''}`); } });
// message loop — exits cleanly on disconnect for { _, raw, err := conn.ReadMessage() if err != nil { break } var msg map[string]interface{} json.Unmarshal(raw, &msg) switch msg["type"] { case "auth_ok": fmt.Printf("connected plan=%v delay=%vms ", msg["plan"], msg["delay_ms"]) case "error": fmt.Printf("error %v: %v ", msg["code"], msg["message"]); break case "plan_updated": // plan changed fmt.Printf("plan updated: %v delay=%vms ", msg["plan"], msg["delay_ms"]) default: // announcement fmt.Printf("[%v] %v ", msg["exchange"], msg["title"]) fmt.Printf(" symbol=%v detected=%v %v ", msg["symbol"], msg["detectedTime"], msg["timezone"]) } }
// message loop — exits cleanly on disconnect while let Some(Ok(raw)) = ws.next().await { let msg: Value = serde_json::from_str(&raw.to_string()).unwrap_or_default(); match msg["type"].as_str().unwrap_or("") { "auth_ok" => println!("connected plan={} delay={}ms", msg["plan"], msg["delay_ms"]), "error" => { println!("error {}: {}", msg["code"], msg["message"]); break; } "plan_updated" => // plan changed println!("plan updated: {} delay={}ms", msg["plan"], msg["delay_ms"]), _ => { // announcement println!("[{}] {}", msg["exchange"], msg["title"]); println!(" symbol={} detected={} {}", msg["symbol"], msg["detectedTime"], msg["timezone"]); } } }
4. Формат получаемых данных
Данные объявления передаются в следующем формате.
symbol может быть строкой, массивом или пустой строкой.
⚠️ Примечания
•
•
•
title, category
Поля передаются на UPBIT · BITHUMB для корейском, и на BINANCE · BYBIT для английском (в оригинале).•
detectedTime —
Время, когда наш движок обнаружил объявление (с точностью до микросекунды)
{ "title": "펏지펭귄(PENGU) 신규 거래지원 안내 (KRW, BTC, USDT 마켓)", "category": "거래", "symbol": "PENGU", "exchange": "Upbit", "detectedTime": "2025-05-09 15:00:00.333851", "timezone": "KST", "url": "https://www.upbit.com/service_center/notice?id=5104&view=share" }
| Поле | Тип | Описание |
|---|---|---|
| title | string | Оригинальный заголовок объявления биржи |
| category | string | Категория объявления — см. список категорий ниже |
| symbol | string | array |
Извлечённый символ монеты. Один: "BTC", несколько: ["BTC","ETH"], пусто: "", тариф Free: "***"
|
| exchange | string |
Биржа-источник: "Upbit", "Bithumb", "Binance", "Bybit"
|
| detectedTime | string | Время, когда наш движок обнаружил объявление (с точностью до микросекунды). Для тарифа Basic применяется дополнительная задержка +30 мс |
| timezone | string | Базовый часовой пояс для detectedTime: KST (Upbit, Bithumb) или UTC (Binance, Bybit) |
| url | string | Ссылка на оригинальное объявление |
Различия тарифов
Все тарифы работают на одной инфраструктуре. Различаются только задержка и видимость символов.
Free
0 мс задержка
Символ *** маскирован
Basic
+30 мс задержка
Полная видимость символа
Premium
0 мс задержка
Полная видимость символа
⚠️ Примечания
• При использовании одного API-ключа одновременное подключение с подключение нескольких устройств в пределах одного региона не допускается. Если соединение уже активно и предпринимается попытка подключения из другого места, новая попытка подключения будет заблокирована с кодом ошибки
• При использовании одного API-ключа одновременное подключение с подключение нескольких устройств в пределах одного региона не допускается. Если соединение уже активно и предпринимается попытка подключения из другого места, новая попытка подключения будет заблокирована с кодом ошибки
4006.
Коды ошибок
При ошибке подключения или аутентификации возвращаются следующие коды.
| CODE | Описание | Способ устранения |
|---|---|---|
| 4001 | Таймаут аутентификации | Отправьте сообщение auth типа auth в течение 5 секунд после подключения. |
| 4002 | Некорректный JSON | Проверьте формат JSON. |
| 4003 | Отсутствует сообщение аутентификации | type: "auth" сообщение обязательно. |
| 4004 | Недействительный ключ | Проверьте ваш ключ в Telegram-боте. |
| 4005 | Подписка истекла | Продлите тариф через Telegram-бота. |
| 4006 | Повторное подключение | Завершите текущий сеанс и подключитесь заново. |
| 4029 | Слишком много запросов (ограничение частоты) | Повторите попытку позже. |
Список категорий
Это список получаемых значений.
category значения поля указаны на корейском для UPBIT · BITHUMB, а на языке оригинала (английском) — для BINANCE · BYBIT.
Полный пример кода
Это полностью рабочий код с автоматическим переподключением и экспоненциальной задержкой повтора. Просто замените
YOUR_API_KEY
и сразу запустите.
import asyncio, json, websockets KEY = "YOUR_API_KEY" # issued via Telegram bot ENDPOINT = "wss://kr.pkcryptolisting.com" RETRIES = 20 def handle_notice(evt: dict): exchange = evt.get("exchange", "") title = evt.get("title", "") category = evt.get("category", "") symbol = evt.get("symbol", "") detected = evt.get("detectedTime", "") timezone = evt.get("timezone", "") url = evt.get("url", "") print(f"[{exchange}] [{category}] {title}") print(f" symbol={symbol}") print(f" detected={detected} {timezone}") print(f" {url}") async def listen(): for attempt in range(RETRIES): try: async with websockets.connect(ENDPOINT) as ws: await ws.send(json.dumps({"type": "auth", "key": KEY})) async for raw in ws: evt = json.loads(raw) evt_type = evt.get("type") if evt_type == "auth_ok": print(f"connected plan={evt['plan']} delay={evt['delay_ms']}ms expires={evt['expires_at']}") attempt = 0 # reset retry counter on success elif evt_type == "error": print(f"[{evt['code']}] {evt['message']}") if evt["code"] in (4004, 4005, 4006, 4029): return # do not reconnect break elif evt_type == "plan_updated": print(f"plan changed → {evt['plan']} delay={evt['delay_ms']}ms expires={evt['expires_at']}") else: handle_notice(evt) except websockets.ConnectionClosed as e: print(f"closed: {e}") except Exception as e: print(f"error: {e}") backoff = min(2 ** attempt, 300) print(f"retry in {backoff}s...") await asyncio.sleep(backoff) if __name__ == "__main__": asyncio.run(listen())
// npm install ws const WebSocket = require("ws"); const KEY = "YOUR_API_KEY"; // issued via Telegram bot const ENDPOINT = "wss://kr.pkcryptolisting.com"; const RETRIES = 20; function handleNotice(evt) { console.log(`[${evt.exchange}] [${evt.category}] ${evt.title}`); console.log(` symbol=${evt.symbol}`); console.log(` detected=${evt.detectedTime} ${evt.timezone || ''}`); console.log(` ${evt.url}`); } function listen(attempt = 0) { if (attempt >= RETRIES) return; const ws = new WebSocket(ENDPOINT); ws.on("open", () => ws.send(JSON.stringify({ type: "auth", key: KEY }))); ws.on("message", (raw) => { const evt = JSON.parse(raw); if (evt.type === "auth_ok") { console.log(`connected plan=${evt.plan} delay=${evt.delay_ms}ms expires=${evt.expires_at}`); attempt = 0; // reset retry counter on success } else if (evt.type === "error") { console.log(`[${evt.code}] ${evt.message}`); if ([4004,4005,4006,4029].includes(evt.code)) return; // do not reconnect ws.close(); } else if (evt.type === "plan_updated") console.log(`plan changed → ${evt.plan} delay=${evt.delay_ms}ms expires=${evt.expires_at}`); else handleNotice(evt); }); ws.on("close", () => { const b = Math.min(2 ** attempt, 300); console.log(`retry in ${b}s...`); setTimeout(() => listen(attempt + 1), b * 1000); }); ws.on("error", (e) => console.log(`error: ${e.message}`)); } listen();
// go get github.com/gorilla/websocket package main import ( "encoding/json"; "fmt"; "math"; "time" "github.com/gorilla/websocket" ) const (key = "YOUR_API_KEY"; endpoint = "wss://kr.pkcryptolisting.com"; retries = 20) // issued via Telegram bot type Evt struct { Type,Plan,Message,ExpiresAt,Exchange,Title,Category,DetectedTime,Timezone,URL string Code,DelayMs int; Symbol interface{} } func handleNotice(e Evt) { fmt.Printf("[%s] [%s] %s symbol=%v detected=%s %s ", e.Exchange, e.Category, e.Title, e.Symbol, e.DetectedTime, e.Timezone, e.URL) } func listen() { for a := 0; a < retries; a++ { c, _, err := websocket.DefaultDialer.Dial(endpoint, nil) if err != nil { fmt.Println("error:", err) } else { b, _ := json.Marshal(map[string]string{"type":"auth","key":key}) c.WriteMessage(websocket.TextMessage, b) for { _, raw, err := c.ReadMessage(); if err != nil { fmt.Println("closed:", err); break } var e Evt; json.Unmarshal(raw, &e) switch e.Type { case "auth_ok": fmt.Printf("connected plan=%s delay=%dms expires=%s ",e.Plan,e.DelayMs,e.ExpiresAt); a=0 // reset retry counter on success case "error": fmt.Printf("[%d] %s ",e.Code,e.Message) if e.Code==4004||e.Code==4005||e.Code==4006||e.Code==4029 { return } // do not reconnect case "plan_updated": fmt.Printf("plan changed → %s delay=%dms expires=%s ",e.Plan,e.DelayMs,e.ExpiresAt) default: handleNotice(e) } }; c.Close() } bf := time.Duration(math.Min(math.Pow(2,float64(a)),300))*time.Second fmt.Printf("retry in %vs... ", bf.Seconds()); time.Sleep(bf) } } func main() { listen() }
use futures_util::{SinkExt, StreamExt}; use tokio_tungstenite::{connect_async, tungstenite::Message}; use serde_json::{json, Value}; use std::time::Duration; const KEY: &str = "YOUR_API_KEY"; // issued via Telegram bot const ENDPOINT: &str = "wss://kr.pkcryptolisting.com"; const RETRIES: u32 = 20; fn handle_notice(e: &Value) { println!("[{}] [{}] {} symbol={} detected={} {} {}", e["exchange"],e["category"],e["title"],e["symbol"],e["detectedTime"],e["timezone"],e["url"]); } #[tokio::main] async fn main() { for attempt in 0..RETRIES { match connect_async(url::Url::parse(ENDPOINT).unwrap()).await { Ok((mut ws, _)) => { ws.send(Message::Text(json!({"type":"auth","key":KEY}).to_string())).await.ok(); while let Some(Ok(raw)) = ws.next().await { if let Ok(e) = serde_json::from_str::<Value>(&raw.to_string()) { match e["type"].as_str().unwrap_or("") { "auth_ok" => println!("connected plan={} delay={}ms expires={}", e["plan"],e["delay_ms"],e["expires_at"]), "error" => { println!("[{}] {}",e["code"],e["message"]); let c=e["code"].as_i64().unwrap_or(0); if [4004,4005,4006,4029].contains(&c) { return; } // do not reconnect break; } "plan_updated" => println!("plan changed → {} delay={}ms expires={}", e["plan"],e["delay_ms"],e["expires_at"]), _ => handle_notice(&e), } } } } Err(e) => println!("error: {}", e), } let b = (2u64.pow(attempt)).min(300); println!("retry in {}s...", b); tokio::time::sleep(Duration::from_secs(b)).await; } }
Получить API-ключ
Получите его бесплатно через Telegram-бота.
Переход на платный тариф также можно оформить через бота.