PKCryptolisting
WebSocket
Guía de integración
Reciba anuncios en tiempo real de Upbit, Bithumb, Binance y Bybit con solo unas pocas líneas de código
Obtenga su API key desde el bot de Telegram y siga la guía a continuación para conectarse.
ENDPOINT · KR
wss://kr.pkcryptolisting.com
Solo Upbit · Bithumb
ENDPOINT · TOKYO
wss://tokyo.pkcryptolisting.com
Upbit · Bithumb · Binance · Bybit
PROTOCOL
WebSocket Secure (WSS)
AUTH
API Key (Emisión vía Telegram)
SERVER LOCATION
AWS Seoul (ap-northeast-2) / AWS Tokyo (ap-northeast-1)
1. Conexión y autenticación
Debe enviar un mensaje de autenticación inmediatamente después de establecer la conexión WebSocket Si no se completa la autenticación en 5 segundosla conexión se cerrará automáticamente
Para recibir avisos solo de una exchange específica, añada el
Para recibir avisos solo de una exchange específica, añada el
exchange campo.
⚠️ Notas
•
•
•
wss://kr.pkcryptolisting.com(Korea) admite únicamente
UPBIT ·
BITHUMB.•
wss://tokyo.pkcryptolisting.com(Tokyo) admite todas las exchanges, incluyendo
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. Respuesta de autenticación
Al autenticarse correctamente, se devuelve información sobre el plan, el retraso y la fecha de vencimiento.
✓ Éxito (auth_ok)
{ "type": "auth_ok", "plan": "premium", "delay_ms": 0, "expires_at": "2026-06-30", "message": "Connected..." }
✕ Fallo (error)
{ "type": "error", "code": 4004, "message": "Invalid key" }
3. Recepción de mensajes
Una vez autenticado, los nuevos anuncios se recibirán automáticamente.
async for interrumpirá automáticamente el bucle cuando la conexión finalice.
# 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. Formato de los datos recibidos
Los datos de los avisos se entregan en el siguiente formato.
symbol puede ser una cadena única, un arreglo o una cadena vacía.
⚠️ Notas
•
•
•
title, category
Los campos se reciben en UPBIT · BITHUMB para coreano, y en BINANCE · BYBIT para el texto original en inglés.•
detectedTime —
El momento en que nuestro motor detectó el anuncio (precisión de microsegundos)
{ "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" }
| Campo | Tipo | Descripción |
|---|---|---|
| title | string | Título original del anuncio de la exchange |
| category | string | Categoría del anuncio — consulte la lista de categorías a continuación |
| symbol | string | array |
Símbolo de moneda extraído. Único: "BTC", Múltiple: ["BTC","ETH"], Vacío: "", Plan Free: "***"
|
| exchange | string |
Exchange de origen del aviso: "Upbit", "Bithumb", "Binance", "Bybit"
|
| detectedTime | string | El momento en que nuestro motor detectó el anuncio (precisión de microsegundos). El plan Basic incluye +30 ms de retraso adicional |
| timezone | string | La zona horaria base de detectedTime. KST (Upbit, Bithumb) o UTC (Binance, Bybit) |
| url | string | Enlace al anuncio original |
Diferencias entre planes
Todos los planes se ejecutan sobre la misma infraestructura. Solo difieren en el retraso y en la visibilidad de los símbolos.
Free
0 ms de retraso
Símbolo *** enmascarado
Basic
+30 ms de retraso
Visibilidad completa de símbolos
Premium
0 ms de retraso
Visibilidad completa de símbolos
⚠️ Notas
• Con una sola API Key, conectarse desde no está permitido conectar varios dispositivos dentro de una misma región. Si ya existe una conexión activa y se intenta otra desde otro lugar, el nuevo intento de conexión será bloqueado con el código de error
• Con una sola API Key, conectarse desde no está permitido conectar varios dispositivos dentro de una misma región. Si ya existe una conexión activa y se intenta otra desde otro lugar, el nuevo intento de conexión será bloqueado con el código de error
4006.
Códigos de error
Los siguientes códigos se devuelven en caso de fallo de conexión o autenticación.
| CODE | Descripción | Resolución |
|---|---|---|
| 4001 | Tiempo de espera de autenticación agotado | Envíe un mensaje auth dentro de los 5 segundos posteriores a la conexión. |
| 4002 | JSON inválido | Verifique el formato JSON. |
| 4003 | Mensaje de autenticación faltante | type: "auth" mensaje requerido. |
| 4004 | Clave inválida | Verifique su clave en el bot de Telegram. |
| 4005 | Suscripción vencida | Renueve su plan a través del bot de Telegram. |
| 4006 | Conexión duplicada | Cierre la sesión existente y vuelva a conectarse. |
| 4029 | Demasiadas solicitudes (límite de tasa) | Inténtelo de nuevo en un momento |
Lista de categorías
Esta es una lista de los valores recibidos. Los valores del campo
category se reciben en UPBIT · BITHUMB, y en el texto original en inglés para BINANCE · BYBIT.
Código de ejemplo completo
Este es un código completo y funcional con reconexión automática y backoff exponencial. Simplemente reemplace
YOUR_API_KEY
y ejecútelo de inmediato.
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; } }
Obtener API Key
Puede obtenerla gratis desde el bot de Telegram.
Las actualizaciones a planes de pago también están disponibles a través del bot.