PKCryptolisting WebSocket
연동 가이드

단 몇 줄의 코드로 업비트·빗썸·바이낸스·바이비트 실시간 공지를 수신할 수 있습니다.
텔레그램 봇에서 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 (텔레그램 발급)
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잘못된 JSONJSON 형식 확인
4003인증 메시지 누락type: "auth" 메시지 전송
4004유효하지 않은 키텔레그램 봇에서 키 확인
4005구독 만료텔레그램 봇에서 플랜 갱신
4006중복 접속기존 연결 종료 후 재접속
4029과도한 요청잠시 후 다시 시도하세요
카테고리 목록
필드에서 수신되는 값 목록입니다. category 필드 값은 UPBIT · BITHUMB은 한국어, BINANCE · BYBIT은 영어 원문으로 수신됩니다.
UPBIT 8개 카테고리
안내일반 서비스 안내
거래신규 마켓 추가 및 상장폐지
입출금입출금 중단·재개
점검서비스 점검
디지털 자산에어드랍 및 기타
NFTNFT 관련 공지
서비스+부가 서비스
이벤트이벤트·프로모션
BITHUMB 12개 카테고리
거래유의거래 유의 종목
거래지원종료상장폐지
공시공시 정보
마켓 추가신규 마켓 추가
수수료 이벤트수수료 혜택
신규서비스신규 서비스 출시
안내일반 안내
업데이트앱·서비스 업데이트
이벤트이벤트·프로모션
입출금입출금 중단·재개
점검서비스 점검
후기빗썸기타 리뷰
BINANCE 8개 카테고리
New Cryptocurrency Listing신규 암호화폐 상장
Delisting상장폐지
Maintenance Updates네트워크 업그레이드 / 지갑 점검
Crypto Airdrop에어드랍
Latest Binance News바이낸스 최신 뉴스
Latest Activities이벤트 / 프로모션
New Fiat Listings신규 법정화폐 페어 상장
API UpdatesAPI 업데이트
BYBIT 9개 카테고리
New Listings신규 상장
Delistings상장폐지
Latest Activities최신 이벤트·활동
Earn예치·이자 상품
Alpha얼리 프로젝트 알림
Latest Bybit News바이비트 뉴스
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 키 발급

텔레그램 봇에서 무료로 발급받을 수 있습니다.
유료 플랜 업그레이드도 봇에서 가능합니다.

텔레그램에서 발급 →