WebSocket API
Hypercall 옵션 거래를 위한 실시간 데이터 스트리밍입니다.
실시간 예제와 스키마 세부 정보를 포함한 더 나은 탐색 환경을 원하신다면 대화형 WebSocket API 레퍼런스를 확인하십시오.
프로그래밍 방식으로 사용하려면 AsyncAPI 명세를 다운로드하십시오.
연결
wss://HOST/ws에 연결합니다:
엔드포인트:
- 프로덕션:
wss://api.hypercall.xyz/ws - 로컬:
ws://localhost:3000/ws
테스트넷은 Hypercall이 더 많은 테스트넷 HYPE를 확보할 때까지 일시적으로 비활성화되어 있습니다.
지갑 식별
인증된 채널(주문, 체결, 포트폴리오)에서 데이터를 수신하려면, 연결 후 Authenticate 메시지를 전송하여 지갑을 식별하십시오:
{"type": "Authenticate", "wallet": "0x1234..."}
서버는 확인 메시지로 응답합니다:
{"type": "Authenticated", "wallet": "0x1234..."}
Authenticated를 수신한 후에는 인증된 채널을 구독할 수 있습니다. 지갑 주소가 유효하지 않으면 서버는 Error 메시지로 응답하며 연결은 열린 상태로 유지됩니다.
?wallet= 쿼리 파라미터는 하위 호환성을 위해 여전히 지원되지만 사용이 중단되었으며 향후 릴리스에서 제거될 예정입니다. 위의 메시지 기반 방식을 사용하시는 것이 좋습니다.
연결 활성 상태
서버는 WebSocket 하트비트를 적용합니다:
- 20초마다
Ping제어 프레임을 전송합니다 - 60초 이내에 일치하는
Pong을 기대합니다 - 클라이언트가 응답을 중단하면 종료 코드
1008과 사유pong timeout으로 연결을 종료합니다
브라우저 WebSocket 구현은 ping/pong을 자동으로 처리합니다. tungstenite 및 tokio-tungstenite를 포함한 많은 Rust websocket 라이브러리도 제어 프레임 ping/pong을 대신 처리해 줍니다. 수동 Pong 처리를 추가하기 전에 클라이언트 라이브러리 문서를 확인하십시오. 커스텀 또는 원시 소켓 구현은 반드시 Ping 프레임에 Pong으로 응답해야 합니다.
느린 소비자 복구
서버는 구성된 메시지, 인코딩된 바이트, 큐 수명, 또는 소켓 쓰기 안전 상한 내에 아웃바운드 데이터를 처리하지 못하는 /ws 연결을 종료합니다. 연결이 여전히 종료 프레임을 수신할 수 있는 경우, 서버는 코드 1008과 간결한 JSON 사유를 사용합니다:
{"error":"slow_consumer","class":"ordered_public","cause":"message_age","recovery":"snapshot_resubscribe"}
사유 필드는 다음과 같습니다:
| 필드 | 의미 |
|---|---|
class | 프레임이 안전 경계를 넘은 전송 클래스입니다. |
cause | message_limit, byte_limit, message_age, 또는 write_timeout입니다. |
recovery | resubscribe, snapshot_resubscribe, portfolio_refetch, 또는 rest_reconcile와 같은 필요한 다음 조치입니다. |
연결이 끊긴 후에는 재연결하고, 필요한 경우 지갑을 다시 식별하고, 재구독한 다음, 새 이벤트를 처리하기 전에 현재 상태를 조정(reconcile)하십시오. 순서가 있는 공개 채널은 새로운 스냅샷을 필요로 합니다. 프라이빗 이벤트 채널은 아직 커서 재생(cursor replay)을 사용할 수 없기 때문에 권위 있는 REST 인터페이스를 통한 조정이 필요합니다. 완전히 정지된 연결은 종료 사유를 읽기 전에 종료될 수 있으므로, 클라이언트는 비정상적인 종료의 경우에도 이 복구 흐름을 사용해야 합니다.
고빈도 공개 시장 데이터와 인증된 명령 또는 프라이빗 스트림에는 별도의 연결을 사용하십시오. 전송 클래스는 메트릭과 복구 동작을 선택하지만, 하나의 연결상의 프레임들은 여전히 하나의 순서가 있는 소켓 쓰기 경로를 공유합니다. 따라서 정지된 공개 쓰기는 쓰기 마감 시한이 연결을 종료할 때까지 동일한 연결상의 이후 프라이빗 프레임을 지연시킬 수 있습니다.
채널 구독
구독하려면 JSON 메시지를 전송합니다:
{"type": "Subscribe", "channel": "orderbook"}
구독을 취소하려면:
{"type": "Unsubscribe", "channel": "orderbook"}
확인 메시지를 수신하게 됩니다:
{"type": "Subscribed", "channel": "orderbook"}
심볼 필터링
order_updates 및 fills 채널은 선택적 symbols 필터를 지원합니다. 제공되면 서버는 기초자산이 지정된 심볼 중 하나와 일치하는 메시지만 전송합니다.
{"type": "Subscribe", "channel": "order_updates", "symbols": ["BTC"]}
단순 기초자산("BTC")과 전체 상품 이름("BTC-20260131-100000-C") 모두 허용됩니다. 심볼을 더 추가하려면 또 다른 Subscribe를 전송하십시오. 특정 심볼을 제거하려면:
{"type": "Unsubscribe", "channel": "order_updates", "symbols": ["BTC"]}
symbols를 지정하지 않으면 지갑에 대한 모든 업데이트가 전달됩니다.
옵션 체인 필터링
options_chain 채널은 기초자산 심볼, 만기일, 옵션 유형별 필터링을 지원합니다:
{
"type": "Subscribe",
"channel": "options_chain",
"symbols": ["BTC-20260131-100000-C"],
"expiry": "2026-01-31",
"option_type": "call"
}
| 필터 | 값 | 기본값 |
|---|---|---|
symbols | 전체 상품 심볼의 배열 (예: ["BTC-20260131-100000-C"]) | 모든 상품 |
expiry | 날짜 문자열 "YYYY-MM-DD" | 모든 만기 |
option_type | "call", "put", 또는 둘 다인 경우 생략 | 둘 다 |
사용 가능한 채널
| 채널 | 인증 필요 | 설명 |
|---|---|---|
orderbook | 아니요 | 모든 심볼에 대한 L2 호가창 업데이트 |
trades | 아니요 | 공개 거래 피드 |
market_updates | 아니요 | 시장 상장 변경 (생성/삭제/만기) |
options_chain | 아니요 | 증분 옵션 체인 업데이트 (symbols, expiry, option_type로 필터링 가능) |
index_prices | 아니요 | 모든 기초자산에 대한 실시간 현물/인덱스 가격 |
indicative_market_data | 아니요 | 허용 목록에 등록된 호가 제공자 스트림. 아직 일반적으로 사용 불가 |
order_updates | 예 | 사용자의 주문 상태 변경 (심볼로 필터링 가능) |
fills | 예 | 사용자의 거래 체결 (심볼로 필터링 가능) |
portfolio | 예 | 사용자의 포지션 및 잔액 업데이트 |
liquidation | 예 | 사용자의 청산 상태 변경 |
competition | 예 | 사용자의 대회 PnL 요약, 순위, 최종 통계 |
competition_engagement | 예 | 순위 변경, 다음 순위까지의 격차, 최종 순위 |
rfq | 예 | RFQ 호가, 상태 업데이트, 체결 알림 |
메시지 유형
주문 제출 (인증 필요)
WebSocket 명령 경로를 통해 주문을 제출합니다.
{
"type": "PlaceOrder",
"wallet": "0x1234...",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"size": "1",
"price": "100",
"tif": "gtc",
"route": "book_only",
"client_id": "my-order-1",
"nonce": 1000,
"signature": "0x..."
}
| 필드 | 유형 | 설명 |
|---|---|---|
wallet | string | 주문을 소유한 지갑 주소 |
symbol | string | 옵션 심볼 |
side | string | "Buy" 또는 "Sell" |
size | string | 계약 크기, 서명된 값과 정확히 일치 |
price | string | 지정가, 서명된 값과 정확히 일치 |
tif | string | 선택적 time-in-force, 기본값은 "gtc" |
route | string | 선택적 라우트. 라우트를 인식하는 WebSocket 주문에는 "book_only"를 사용하십시오. 라우트를 생략하는 경우 최소 2026년 7월 4일까지는 계속 허용됩니다. |
client_id | string | 선택적 클라이언트 주문 ID |
nonce | integer | 고유 서명 nonce |
signature | string | EIP-712 PlaceOrder 서명 |
WebSocket PlaceOrder는 현재 호가창으로 직접 전달됩니다. 이 경로는 아직 RPI/RFQ 라우팅을 실행하지 않으므로 route="best_execution"과 route="rfq_only"는 WebSocket에서 거부됩니다. best_execution에는 POST /order를 사용하십시오.
호가창 업데이트
심볼에 대한 L2 호가창 스냅샷/업데이트입니다.
{
"type": "OrderbookUpdate",
"symbol": "BTC-20260131-100000-C",
"bids": [["95000.5", "10.5"], ["94999.0", "25.0"]],
"asks": [["95001.0", "8.0"], ["95002.5", "15.0"]],
"timestamp": 1737331200000
}
| 필드 | 유형 | 설명 |
|---|---|---|
symbol | string | 옵션 심볼 |
bids | array | [price, size] 튜플로 표현된 매수호가 레벨, size는 사람이 읽을 수 있는 계약 단위 |
asks | array | [price, size] 튜플로 표현된 매도호가 레벨, size는 사람이 읽을 수 있는 계약 단위 |
timestamp | integer | Unix 타임스탬프 (밀리초) |
거래
공개 거래 이벤트입니다.
{
"type": "Trade",
"symbol": "BTC-20260131-100000-C",
"price": "0.0523",
"size": "5.0",
"side": "buy",
"timestamp": 1737331200000
}
| 필드 | 유형 | 설명 |
|---|---|---|
symbol | string | 옵션 심볼 |
price | string | USD 단위 거래 가격 |
size | string | 계약 단위 거래 크기 |
side | string | 공격자 측 (buy 또는 sell) |
timestamp | integer | Unix 타임스탬프 (밀리초) |
체결 (인증 필요)
사용자의 거래 체결 알림입니다.
{
"type": "Fill",
"order_id": 12345,
"fill_id": 67890,
"symbol": "BTC-20260131-100000-C",
"side": "buy",
"price": "0.0523",
"size": "5.0",
"timestamp": 1737331200000,
"wallet_address": "0x1234...abcd",
"fee": "0",
"trade_id": 99999,
"is_taker": true
}
| 필드 | 유형 | 설명 |
|---|---|---|
order_id | integer | 주문 ID |
fill_id | integer | 체결 ID |
symbol | string | 옵션 심볼 |
side | string | 매매 방향 (buy 또는 sell) |
price | string | USD 기준 체결 가격 |
size | string | 계약 수 기준 체결 수량 |
timestamp | integer | Unix 타임스탬프 (밀리초) |
wallet_address | string | 사용자의 지갑 주소 |
fee | string | 부과된 거래 수수료. 런치 베뉴 수수료가 비활성화된 동안에는 0을 반환합니다 |
trade_id | integer | 고유 거래 ID |
is_taker | boolean | 사용자가 테이커였는지 여부 |
builder_code_address | string? | 빌더 코드 지갑 (있는 경우) |
builder_code_fee | string? | 빌더 코드 수수료. 런치 베뉴 수수료가 비활성화된 동안에는 null을 반환합니다 |
포트폴리오 업데이트 (인증 필요)
포지션, 잔고, 증거금, 그릭스에 대한 포트폴리오 스트림 업데이트입니다.
그릭스 업데이트 예시:
{
"type": "PortfolioUpdate",
"timestamp": 1737331200000,
"per_leg": [
{
"symbol": "BTC-20260131-100000-C",
"quantity": "2.0",
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
],
"aggregate": {
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
}
빈 포트폴리오의 경우, 그릭스 업데이트는 다음을 사용합니다:
per_leg: []aggregate: null
대회 손익 요약 (인증 필요)
헤더/푸터 손익 표시를 위한 대회 스트림 업데이트입니다.
{
"type": "CompetitionPnlSummary",
"wallet_address": "0x1234...abcd",
"lifetime_realized_pnl": "1250.50",
"active_competition": {
"competition_id": 7,
"competition_name": "Spring Sprint",
"competition_state": "active",
"rank": 12,
"pnl": "420.25",
"volume": "25000",
"efficiency": "0.01681",
"medal": null
},
"timestamp": 1737331200000
}
진행 중인 대회가 없는 경우, active_competition은 null입니다.
주문 업데이트 (인증 필요)
주문 상태 변경 알림입니다.
{
"type": "OrderUpdate",
"order_id": 12345,
"client_order_id": "my-order-1",
"status": "filled",
"filled_size": "10.0",
"remaining_size": "0",
"avg_fill_price": "0.0523"
}
마켓 업데이트
마켓 상장 변경 사항입니다.
마켓 생성:
{
"type": "MarketUpdate",
"action": "Created",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1737331200000
}
마켓 만기:
{
"type": "MarketUpdate",
"action": "Expired",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1738281600000
}
포지션 만기 (인증 필요)
사용자의 포지션이 만기에 결제될 때 발생하는 알림입니다.
{
"type": "PositionExpired",
"wallet_address": "0x1234...abcd",
"symbol": "BTC-20260131-100000-C",
"position_size": "10.0",
"settlement_price": "105000",
"settlement_value": "500.0",
"timestamp": 1738281600000
}
청산 상태 변경 (인증 필요)
사용자 계정의 청산 상태 변경입니다.
{
"type": "LiquidationStateChange",
"wallet_address": "0x1234...abcd",
"previous_state": "Normal",
"new_state": "Warning",
"equity": "10000.0",
"mm_required": "9500.0",
"shortfall": "0",
"auction_id": null,
"timestamp": 1737331200000
}
| 상태 | 설명 |
|---|---|
Normal | 계정이 정상 상태입니다 |
Warning | 마진콜에 근접하고 있습니다 |
Liquidating | 청산 경매가 진행 중입니다 |
인덱스 가격 업데이트
모든 기초자산에 대한 일괄 현물/인덱스 가격입니다.
{
"type": "IndexPriceUpdate",
"prices": [
{"underlying": "BTC", "price": "97250.50"},
{"underlying": "ETH", "price": "3200.00"},
{"underlying": "HYPE", "price": "28.50"}
],
"timestamp": 1737331200000
}
| 필드 | 유형 | 설명 |
|---|---|---|
prices | array | 추적 대상 각 기초자산에 대한 {underlying, price} 항목의 배열 |
prices[].underlying | string | 기초자산 심볼 (예: "BTC", "ETH") |
prices[].price | string | USD 기준 현재 현물/인덱스 가격 |
timestamp | integer | Unix 타임스탬프 (밀리초) |
인디케이티브 마켓 데이터
등록된 호가 제공자로부터 집계된 최우선 매수호가/매도호가를 제공하는 허용 목록 기반 호가 제공자 스트림입니다. 이 채널은 아직 일반적으로 사용할 수 없습니다. Hypercall이 사용자의 연동을 위해 호가 제공자 스트리밍을 활성화하지 않은 경우, REST 마켓 데이터와 인증된 주문/체결/포트폴리오 채널을 사용하십시오.
{
"type": "IndicativeMarketData",
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"indicative_bid_size": "50.0",
"indicative_ask_size": "25.0",
"num_providers": 3,
"timestamp": 1737331200000
}
| 필드 | 유형 | 설명 |
|---|---|---|
instrument | string | 옵션 심볼 |
best_bid | string | 선택적 최우선 집계 매수호가 |
best_ask | string | 선택적 최우선 집계 매도호가 |
bid_iv | number | 선택적 최우선 매수호가의 내재변동성 |
ask_iv | number | 선택적 최우선 매도호가의 내재변동성 |
indicative_bid_size | string | 선택적 제공자 전체의 총 매수 수량 |
indicative_ask_size | string | 선택적 제공자 전체의 총 매도 수량 |
num_providers | integer | 활성 호가 제공자 수 |
timestamp | integer | Unix 타임스탬프 (밀리초) |
대회 순위 변경 (인증 필요)
진행 중인 대회에서 사용자의 순위가 변경될 때 발생하는 알림입니다.
{
"type": "CompetitionRankChange",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"from_rank": 15,
"to_rank": 12,
"delta_places": 3,
"pnl": "420.25",
"timestamp": 1737331200000
}
대회 격차 업데이트 (인증 필요)
바로 위 순위까지의 격차입니다.
{
"type": "CompetitionGapUpdate",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"rank": 12,
"next_rank": 11,
"gap_metric_value": "50.00",
"timestamp": 1737331200000
}
대회 최종 순위 (인증 필요)
대회가 종료될 때 사용자의 최종 결과와 함께 전송됩니다.
{
"type": "CompetitionFinalStanding",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"rank": 12,
"pnl": "420.25",
"volume": "25000",
"efficiency": "0.01681",
"medal": null,
"timestamp": 1737331200000
}
RFQ 호가 (인증 필요)
사용자의 RFQ 제출에 대한 응답으로 수신된 호가입니다.
{
"type": "RfqQuotes",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"quotes": [
{
"quote_id": "660e8400-e29b-41d4-a716-446655440001",
"net_premium": "52.30",
"expires_at": 1737331225000
}
],
"status": "quoted",
"taker_wallet": "0x1234...abcd"
}
RFQ 상태 업데이트 (인증 필요)
사용자가 제출한 RFQ의 상태 변경입니다.
{
"type": "RfqStatusUpdate",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "executed",
"taker_wallet": "0x1234...abcd"
}
오류
서버 오류 메시지입니다.
{
"type": "Error",
"message": "Invalid channel: foobar"
}
인증
인증된 채널은 연결 후 지갑 식별 메시지를 필요로 합니다:
{"type": "Authenticate", "wallet": "0x1234567890abcdef..."}
인증된 채널의 메시지는 사용자의 지갑에 대한 데이터만 표시하도록 필터링됩니다. WebSocket 연결에는 서명이 필요하지 않습니다.
예시: Python 클라이언트
import asyncio
import websockets
import json
async def main():
uri = "wss://api.hypercall.xyz/ws"
async with websockets.connect(uri) as ws:
# Identify the wallet before subscribing to authenticated channels.
await ws.send(json.dumps({
"type": "Authenticate",
"wallet": "0xYourWallet"
}))
# Subscribe to orderbook
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "orderbook"
}))
# Subscribe to fills for BTC only
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "fills",
"symbols": ["BTC"]
}))
# Listen for messages
async for message in ws:
data = json.loads(message)
print(f"Received: {data['type']}")
asyncio.run(main())
예시: TypeScript 클라이언트
const ws = new WebSocket("wss://api.hypercall.xyz/ws");
ws.onopen = () => {
ws.send(JSON.stringify({ type: "Authenticate", wallet: "0xYourWallet" }));
// Subscribe to channels
ws.send(JSON.stringify({ type: "Subscribe", channel: "orderbook" }));
// Subscribe to order updates filtered to BTC
ws.send(JSON.stringify({
type: "Subscribe",
channel: "order_updates",
symbols: ["BTC"],
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(`Received: ${msg.type}`);
if (msg.type === "OrderbookUpdate") {
console.log(`${msg.symbol}: ${msg.bids.length} bids, ${msg.asks.length} asks`);
}
};