PKCryptolisting
WebSocket
Hướng dẫn tích hợp
Nhận thông báo theo thời gian thực từ Upbit, Bithumb, Binance và Bybit chỉ với vài dòng mã.
Lấy API key từ bot Telegram và làm theo hướng dẫn bên dưới để kết nối.
ENDPOINT · KR
wss://kr.pkcryptolisting.com
Chỉ Upbit · Bithumb
ENDPOINT · TOKYO
wss://tokyo.pkcryptolisting.com
Upbit · Bithumb · Binance · Bybit
PROTOCOL
WebSocket Secure (WSS)
AUTH
API Key (Cấp qua Telegram)
SERVER LOCATION
AWS Seoul (ap-northeast-2) / AWS Tokyo (ap-northeast-1)
1. Kết nối & Xác thực
Bạn phải gửi tin nhắn xác thực ngay sau khi thiết lập kết nối WebSocket Nếu không xác thực trong vòng 5 giâykết nối sẽ tự động chấm dứt
Để chỉ nhận thông báo từ một sàn giao dịch cụ thể, thêm
Để chỉ nhận thông báo từ một sàn giao dịch cụ thể, thêm
exchange trường.
⚠️ Lưu ý
•
•
•
wss://kr.pkcryptolisting.com(Korea) endpoint chỉ hỗ trợ
UPBIT ·
BITHUMB.•
wss://tokyo.pkcryptolisting.com(Tokyo) endpoint hỗ trợ tất cả sàn giao dịch bao gồm
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. Phản hồi xác thực
Khi xác thực thành công, thông tin về gói, độ trễ và ngày hết hạn sẽ được trả về.
✓ Thành công (auth_ok)
{ "type": "auth_ok", "plan": "premium", "delay_ms": 0, "expires_at": "2026-06-30", "message": "Connected..." }
✕ Thất bại (error)
{ "type": "error", "code": 4004, "message": "Invalid key" }
3. Nhận tin nhắn
Sau khi xác thực, các thông báo mới sẽ được nhận tự động khi phát hiện.
async for sẽ tự động thoát vòng lặp khi kết nối chấm dứt.
# 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. Định dạng dữ liệu nhận
Dữ liệu thông báo được gửi theo định dạng sau.
symbol có thể là một chuỗi đơn, một mảng, hoặc một chuỗi rỗng.
⚠️ Lưu ý
•
•
•
title, category
Các trường được nhận bằng UPBIT · BITHUMB cho tiếng Hàn, và bằng BINANCE · BYBIT cho tiếng Anh gốc.•
detectedTime —
Thời điểm hệ thống của chúng tôi phát hiện thông báo (độ chính xác micro giây)
{ "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" }
| Trường | Loại | Mô tả |
|---|---|---|
| title | string | Tiêu đề gốc của thông báo sàn giao dịch |
| category | string | Danh mục thông báo — tham khảo danh sách danh mục bên dưới |
| symbol | string | array |
Ký hiệu coin được trích xuất. Đơn lẻ: "BTC", Nhiều: ["BTC","ETH"], Trống: "", Gói Free: "***"
|
| exchange | string |
Sàn giao dịch nguồn: "Upbit", "Bithumb", "Binance", "Bybit"
|
| detectedTime | string | Thời điểm hệ thống của chúng tôi phát hiện thông báo (độ chính xác micro giây). Gói Basic có +30ms độ trễ bổ sung |
| timezone | string | Múi giờ cơ sở cho detectedTime. KST (Upbit, Bithumb) hoặc UTC (Binance, Bybit) |
| url | string | Liên kết đến thông báo gốc |
Khác biệt giữa các gói
Tất cả các gói đều chạy trên cùng hạ tầng. Chỉ khác nhau về độ trễ và khả năng hiển thị ký hiệu.
Free
0ms độ trễ
Ký hiệu *** Đã che
Basic
+30ms độ trễ
Hiển thị đầy đủ ký hiệu
Premium
0ms độ trễ
Hiển thị đầy đủ ký hiệu
⚠️ Lưu ý
• Với một API Key duy nhất, việc kết nối từ không cho phép nhiều thiết bị trong cùng một khu vực. Nếu một kết nối đang hoạt động và có nỗ lực kết nối khác từ nơi khác, nỗ lực kết nối mới sẽ bị chặn với mã lỗi
• Với một API Key duy nhất, việc kết nối từ không cho phép nhiều thiết bị trong cùng một khu vực. Nếu một kết nối đang hoạt động và có nỗ lực kết nối khác từ nơi khác, nỗ lực kết nối mới sẽ bị chặn với mã lỗi
4006.
Mã lỗi
Các mã sau đây sẽ được trả về khi kết nối hoặc xác thực thất bại.
| CODE | Mô tả | Cách xử lý |
|---|---|---|
| 4001 | Hết thời gian xác thực | Gửi tin nhắn auth trong vòng 5 giây sau khi kết nối. |
| 4002 | JSON không hợp lệ | Kiểm tra định dạng JSON. |
| 4003 | Thiếu tin nhắn xác thực | type: "auth" tin nhắn bắt buộc. |
| 4004 | Key không hợp lệ | Kiểm tra key của bạn trong bot Telegram. |
| 4005 | Gói đăng ký hết hạn | Gia hạn gói qua bot Telegram. |
| 4006 | Kết nối trùng lặp | Ngắt kết nối hiện có và kết nối lại. |
| 4029 | Quá nhiều yêu cầu (Giới hạn tốc độ) | Vui lòng thử lại sau. |
Danh mục
Đây là danh sách các giá trị được nhận. Giá trị trường
category được nhận bằng tiếng Hàn cho UPBIT · BITHUMB, và bằng tiếng Anh gốc cho BINANCE · BYBIT.
Mã ví dụ đầy đủ
Đây là mã hoàn chỉnh có tự động kết nối lại và exponential backoff. Chỉ cần thay thế
YOUR_API_KEY
và chạy ngay lập tức.
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; } }
Cấp API Key
Bạn có thể nhận miễn phí từ bot Telegram.
Cũng có thể nâng cấp gói trả phí thông qua bot.