PKCryptolisting WebSocket
串接指南

僅需幾行程式碼,即可接收 Upbit、Bithumb、Binance、Bybit 的即時公告。
請先於 Telegram 機器人取得 API 金鑰,再依下方指南進行連接。

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"
}
欄位類型說明
titlestring交易所公告原始標題
categorystring公告分類 — 請參閱下方分類清單
symbolstring | array 擷取之幣種代號。單一:"BTC",多個:["BTC","ETH"],空值:"",Free 方案:"***"
exchangestring 公告來源交易所:"Upbit""Bithumb""Binance""Bybit"
detectedTimestring 我方引擎偵測到公告之時間點(微秒級精度)。Basic 方案套用 +30ms 額外延遲
timezonestring detectedTime 所使用的基準時區。KST(Upbit、Bithumb)或 UTC(Binance、Bybit)
urlstring公告原文連結
方案差異
所有方案皆使用相同的基礎架構,僅延遲時間與幣種公開與否有所不同。
Free
0ms 延遲
幣種代號 *** 遮罩
Basic
+30ms 延遲
幣種代號完全公開
Premium
0ms 延遲
幣種代號完全公開
⚠️ 注意事項
使用單一 API Key 無法同時從 同一區域內無法從多台裝置連線若已存在連線,其他位置嘗試連線時,新連線嘗試將以錯誤代碼 4006遭到封鎖。
錯誤代碼
連線或驗證失敗時,將回傳下列代碼。
CODE說明處理方式
4001驗證逾時連線後 5 秒內傳送 auth 訊息。
4002無效的 JSON請確認 JSON 格式。
4003缺少驗證訊息type: "auth"須傳送驗證訊息。
4004無效的金鑰於 Telegram 機器人確認金鑰
4005訂閱已到期於 Telegram 機器人更新方案
4006重複連線請先中斷既有連線後再重新連接
4029請求過於頻繁(速率限制)請稍後再試
分類清單
這是所接收到的數值清單。category 欄位值在 UPBIT · BITHUMB 為韓文,在 BINANCE · BYBIT 則為英文原文。
UPBIT 8 個分類
안내一般服務公告
거래新增市場及下市
입출금出入金暫停及恢復
점검系統維護
디지털 자산空投及其他
NFTNFT 相關公告
서비스+附加服務
이벤트活動 & 促銷
BITHUMB 12 個分類
거래유의交易注意標的
거래지원종료終止交易支援(下市)
공시公告揭露資訊
마켓 추가新增市場
수수료 이벤트手續費優惠
신규서비스新服務上線
안내一般公告
업데이트應用程式 & 服務更新
이벤트活動 & 促銷
입출금出入金暫停及恢復
점검系統維護
후기Bithumb 等其他評論
BINANCE 8 個分類
New Cryptocurrency Listing新加密貨幣上市
Delisting下市公告
Maintenance Updates網路升級/錢包維護
Crypto Airdrop空投
Latest Binance NewsBinance 最新消息
Latest Activities最新活動 & 促銷
New Fiat Listings新增法幣交易對上市
API UpdatesAPI 更新
BYBIT 9 個分類
New Listings新幣上市
Delistings下市公告
Latest Activities最新活動
Earn存款 & 理財商品
Alpha早期專案通知
Latest Bybit NewsBybit 最新消息
Maintenance Updates維護更新
Partnership Announcement合作夥伴公告
Listing Billboard上市公告板
完整範例程式碼
這是包含自動重連與指數退避機制的完整可執行程式碼。只需替換 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 機器人免費取得。
付費方案升級亦可透過機器人完成。

透過 Telegram 取得 →