실시간 배당 API: WebSocket으로 받는 실시간 배당률
실시간 배당 API는 북메이커의 배당률이 바뀌는 순간 그 가격을 코드로 보내줍니다. Odds-API.io는 34개 스포츠, 450개 이상 북메이커의 프리매치 및 인플레이 배당률을 하나의 WebSocket 연결로 스트리밍하며, 같은 스트림에 실시간 스코어, 경기 상태, 경기 시계도 함께 담아 보냅니다.
WebSocket 스트리밍은 모든 유료 플랜에 추가할 수 있는 애드온입니다. REST는 모든 플랜에 포함됩니다.
- 북메이커
- 450+
- 스포츠
- 34
- 지연 시간
- <150ms
- 리플레이 기간
- 24h
폴링 대신 WebSocket을 쓰는 이유
폴링은 정해진 주기로 배당률을 요청하고 대부분 바뀌지 않은 데이터를 돌려받습니다. WebSocket은 연결을 유지한 채 움직인 가격만 전달하므로 변화를 더 빨리 확인하고 주고받는 데이터도 훨씬 적습니다.
| 항목 | REST 폴링 | WebSocket |
|---|---|---|
| 변경 사항 수신 방식 | 정해진 주기로 다시 요청 | 가격이 바뀌는 즉시 푸시 |
| 지연 시간 | 폴링 주기 + 요청 시간 | <150ms |
| 연결 | 폴링할 때마다 새 HTTP 요청 | 계속 유지되는 하나의 연결 |
| 내려간 마켓 | 경기를 다시 조회해야 알 수 있음 | 명시적인 deleted 및 no_markets 메시지 |
| 적합한 용도 | 스크립트, 백필, 간단한 조회 | 라이브 베팅 앱, 인플레이 모델, 아비트라지 스캐너 |
몇 줄이면 연결됩니다
API 키와 원하는 마켓을 지정해 wss://api.odds-api.io/v3/ws에 연결하세요. 첫 메시지는 필터 설정을 확인해 주는 환영 메시지이며, 이후 배당률이 바뀔 때마다 도착합니다.
// npm i ws
import WebSocket from 'ws'
const API = 'https://api.odds-api.io/v3'
const apiKey = process.env.ODDS_API_KEY
const markets = 'ML,Spread,Totals'
const eventIds = '63017989,63017990' // up to 10, for example from GET /v3/events/live
const bookmakers = 'Bet365,Unibet' // the bookmakers selected on your account
const board = new Map()
let lastSeq = 0
let needsRebuild = false
function connect() {
const params = new URLSearchParams({ apiKey, markets, eventIds, channels: 'odds,scores,status' })
if (lastSeq) params.set('lastSeq', String(lastSeq))
const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`)
ws.on('message', (raw) => {
const msg = JSON.parse(raw)
if (msg.type === 'resync_required') {
needsRebuild = true
return ws.close()
}
if (msg.seq > lastSeq) lastSeq = msg.seq
const key = `${msg.id}:${msg.bookie}`
if (msg.type === 'created' || msg.type === 'updated') board.set(key, msg.markets)
if (msg.type === 'deleted' || msg.type === 'no_markets') board.delete(key)
if (msg.type === 'score') console.log(msg.id, msg.scores.home, msg.scores.away)
})
ws.on('error', (err) => console.error('WebSocket error:', err.message || err.code))
ws.on('close', async () => {
if (needsRebuild) lastSeq = await rebuildFromRest()
setTimeout(connect, 2000)
})
}
// Replay was not possible: reload the subscription from REST and resume from its seq
async function rebuildFromRest() {
for (let attempt = 1; ; attempt++) {
try {
const params = new URLSearchParams({ apiKey, eventIds, bookmakers, markets, includeSeq: 'true' })
const res = await fetch(`${API}/odds/multi?${params}`)
const seq = Number(res.headers.get('X-OddsAPI-Seq'))
if (!res.ok || !(seq > 0)) throw new Error(`snapshot not usable: ${res.status}`)
const events = await res.json()
board.clear()
for (const event of events) {
for (const [bookie, odds] of Object.entries(event.bookmakers)) board.set(`${event.id}:${bookie}`, odds)
}
needsRebuild = false
return seq
} catch (err) {
console.error(`${err.message}, retry ${attempt}`)
await new Promise((resolve) => setTimeout(resolve, Math.min(30000, 1000 * 2 ** attempt)))
}
}
}
connect()# pip install websockets
import asyncio
import json
import os
from urllib.parse import urlencode
import websockets
params = urlencode({
"apiKey": os.environ["ODDS_API_KEY"],
"markets": "ML,Spread,Totals",
"leagues": "england-premier-league",
"status": "live",
})
async def main():
async with websockets.connect(f"wss://api.odds-api.io/v3/ws?{params}") as ws:
async for message in ws:
msg = json.loads(message)
if msg["type"] == "updated":
for market in msg["markets"]:
print(msg["id"], msg["bookie"], market["name"], market["odds"][0])
asyncio.run(main())압축은 필수입니다
클라이언트는 연결 시 permessage-deflate를 지원해야 합니다. 대역폭이 3~5배 줄어들며, 압축 없이 연결하면 HTTP 426으로 거부됩니다. 대부분의 라이브러리가 자동으로 처리합니다.
| 플랫폼 | 라이브러리 | 압축 |
|---|---|---|
| Node.js | ws | 기본 활성화 |
| 브라우저 | 네이티브 WebSocket | 기본 활성화 |
| Python | websockets | 기본 활성화 |
| Python | websocket-client | 지원 안 함, websockets 사용 |
| PHP | amphp/websocket-client | 기본 활성화 (zlib 필요) |
| PHP | textalk/websocket | 지원 안 함, amphp/websocket-client 사용 |
| Go | gorilla/websocket | EnableCompression = true 설정 |
| .NET | ClientWebSocket | DangerousDeflateOptions 설정 (.NET 6+) |
메시지 유형
모든 메시지는 type 필드가 있는 JSON 객체입니다. 배당률 메시지는 /v3/odds 엔드포인트와 같은 형식이므로 파서 하나로 둘 다 처리할 수 있습니다.
created- 북메이커가 구독 중인 경기를 새로 추가했습니다
updated- 배당률이 바뀌었습니다. 해당 경기와 북메이커에 대해 구독한 마켓의 현재 전체 데이터가 담겨 있습니다
deleted- 경기가 삭제되었거나 해당 북메이커가 중단되었습니다
no_markets- 경기는 아직 목록에 있지만 구독한 마켓이 하나도 열려 있지 않습니다
score- 실시간 스코어가 바뀌었습니다 (scores 채널)
status- 경기가 대기, 진행 중, 정산, 취소 상태로 바뀌었으며 현재 스코어를 함께 보냅니다 (status 채널)
clock- 경기 시계가 돌아가는 진행 중인 경기마다 약 30초 간격으로 보내는 경기 시계 스냅샷입니다
병합하지 말고 교체하세요
중단된 마켓은 별도 표시 없이 다음 updated 메시지에서 빠집니다. 메시지를 받을 때마다 해당 경기와 북메이커에 대해 보관 중인 데이터를 덮어쓰면, 내려간 가격은 앱에서 자연스럽게 사라집니다.
{
"type": "updated",
"seq": 482917,
"timestamp": 1723992773,
"id": "63017989",
"bookie": "Bet365",
"markets": [
{
"name": "ML",
"updatedAt": "2026-11-08T15:42:10Z",
"odds": [{ "home": "1.85", "draw": "3.25", "away": "2.10" }]
},
{
"name": "Totals",
"updatedAt": "2026-11-08T15:42:10Z",
"odds": [{ "hdp": 2.5, "over": "1.85", "under": "2.10" }]
}
]
}채널과 필터
channels로 받을 스트림을 고른 다음 쿼리 파라미터로 범위를 좁혀, 화면에 표시할 경기만 받으세요. leagues와 eventIds는 함께 사용할 수 없습니다.
채널
odds- 기본 채널입니다. 계정에서 선택한 북메이커의
created,updated,deleted,no_markets메시지 scores- 스코어가 바뀔 때마다 오는
score메시지와clock스냅샷 status- 대기, 진행 중, 정산, 취소에 대한
status메시지와clock스냅샷
필터
markets- odds 채널에서는 필수입니다. 최대 40개 마켓 이름, 예:
ML,Spread,Totals sport- 최대 10개 스포츠, 예:
football,basketball leagues- 최대 20개 리그 슬러그, 예:
england-premier-league eventIds- 최대 50개 경기 ID
statuslive는 인플레이만,prematch는 프리매치만
channels는 허용 목록입니다. 배당률과 스코어를 함께 받으려면 channels=odds,scores,status를 보내세요. channels=scores,status만 보내면 배당률은 오지 않습니다.
끊김 없이 재연결하기
네트워크는 끊기기 마련입니다. 모든 배당률 메시지에는 seq 번호가 있으며, 피드는 이 번호로 재연결 후 처음부터 다시 시작하지 않고 놓친 부분을 따라잡게 해줍니다.
- 1
마지막 seq 저장
받은
seq중 가장 큰 값을 저장하세요. 한 연결에서 seq는 증가하기만 하지만 모든 구독자가 공유하므로, 번호가 건너뛰어도 누락이 아닙니다. - 2
lastSeq로 재연결
연결 URL에
lastSeq를 추가하세요. 연결이 끊긴 동안 바뀐 모든 경기와 북메이커의 최신 상태를 먼저 받고, 이어서 실시간 스트림이 계속됩니다. - 3
실시간 복귀 시점 확인
replayComplete=true를 추가하면 따라잡기가 끝났을 때replay_complete메시지를 한 번 받습니다. 리플레이 데이터는 최대 24시간 보관됩니다. - 4
요청 시 다시 구성
깔끔한 리플레이가 불가능하면
resync_required를 받습니다.includeSeq=true로/v3/odds에서 스냅샷을 가져온 뒤X-OddsAPI-Seq헤더의 seq로 다시 연결하세요.
API 키 하나당 동시에 두 개의 연결을 유지할 수 있습니다. 예를 들어 기본 연결과 대기 연결입니다. 더 필요하면 요청해 주세요.
실시간 스코어와 경기 시계
인플레이 배당률은 스코어와 함께 볼 때 더 의미가 있습니다. 같은 연결에서 scores와 status 채널을 구독하거나, REST로 현재 상태를 조회하세요.
- GET /v3/events/live
- 지금 진행 중인 모든 경기와 스코어,
clock객체. 스포츠별로 필터링 가능 - clock
minute,playedSeconds,period,running,statusDetail, 그리고 축구의 추가 시간과 테니스의 서브 선수- 구간별 스코어
- 하프, 쿼터, 세트, 연장전, 승부차기를 모든 스포츠에서 같은 키 체계로 제공
- 흐르는 시계
running이 true인 동안playedSeconds부터 시간을 세고, 스냅샷을 받을 때마다 서버 값으로 맞추세요
REST 대안: /odds와 /odds/updated
모든 플랜에 REST API가 포함되며, 같은 실시간 배당률을 제공합니다. 스냅샷 용도로 쓰거나 소켓이 맞지 않는 곳에서 폴링하세요.
- GET /v3/odds
- 선택한 북메이커 기준 한 경기의 전체 스냅샷.
/v3/odds/multi는 한 번의 요청으로 최대 10개 경기를 처리합니다 - GET /v3/odds/updated
- 진짜 델타입니다.
since이후 바뀐 마켓만 반환하며, 이 시점은 최대 90초 전까지 가능합니다. 응답 헤더X-Next-Since를 다음 호출의since로 넘기면 공백 없이 이어집니다 - includeSeq=true
- 현재 스트림 위치를
X-OddsAPI-Seq로 추가해, REST 스냅샷에서 WebSocket으로 업데이트 누락 없이 넘어갈 수 있게 합니다
델타로는 마켓이 내려갔다는 사실을 알 수 없으므로, 폴링 클라이언트는 가끔 /v3/odds로 경기를 다시 조회해야 합니다. WebSocket은 대신 deleted와 no_markets를 보냅니다.
실시간 배당 피드 지원 범위
- 북메이커
- 450개 이상. Bet365, DraftKings, FanDuel, BetMGM, Betfair Exchange와 유럽, 아시아, 호주, 중남미의 현지 북메이커 포함
- 스포츠
- 34개. 축구, 농구, 테니스, 미식축구, 야구, 아이스하키, 크리켓, e스포츠 등
- 인플레이
- 인플레이 배당을 제공하는 모든 북메이커의 라이브 배당률, 약 1초마다 갱신
- 마켓
- 머니라인, 스프레드, 토탈, 아시안 핸디캡, 코너킥, 카드, 선수 프롭 등 (북메이커에 따라 다름)
- 지연 시간
- WebSocket으로 푸시되는 업데이트 기준 <150ms
- 형식
/v3/odds와 같은 JSON, WebSocket과 REST로 제공
플랜과 WebSocket 이용
모든 유료 플랜에 실시간 배당률을 제공하는 REST API가 포함됩니다. WebSocket 스트리밍은 어떤 플랜에도 추가할 수 있는 애드온입니다.
- 애드온 요금은 플랜 요금과 같아 총 플랜 요금의 2배이며, 월간 또는 연간으로 결제할 수 있습니다
- 스트림에는 계정에서 선택한 모든 북메이커가 포함되므로, 받을 수 있는 북메이커 수는 플랜에 따라 정해집니다
- 스코어, 경기 상태, 경기 시계가 같은 연결로 전달됩니다
- 매월 언제든 해지 가능
실시간 배당 API 자주 묻는 질문
실시간 배당 API란 무엇인가요?
실시간 배당 API는 진행 중인 경기의 현재 베팅 배당률을 JSON으로 코드에 전달하고, 북메이커가 가격을 움직일 때마다 최신 상태로 유지해 줍니다. Odds-API.io는 450개 이상 북메이커의 라이브 및 프리매치 배당률을 WebSocket으로 스트리밍하며, 같은 배당률을 REST로도 제공합니다.
실시간 배당률은 얼마나 빠른가요?
WebSocket 업데이트는 <150ms 지연 시간으로 도착하며, 인플레이 배당률은 약 1초마다 갱신됩니다. REST 폴링은 여기에 폴링 주기가 더해지므로, 인플레이 앱에는 WebSocket을 권장합니다.
WebSocket 연결이 끊기면 어떻게 되나요?
마지막으로 받은 seq를 lastSeq 파라미터에 넣어 재연결하세요. 최대 24시간의 공백까지 서버가 그동안 바뀐 모든 항목의 최신 상태를 다시 보내고 실시간 전송을 이어갑니다. 깔끔한 리플레이가 불가능하면 resync_required를 보내며, 이때는 REST 스냅샷으로 다시 구성하면 됩니다.
어떤 WebSocket 클라이언트를 사용할 수 있나요?
permessage-deflate 압축을 지원하는 클라이언트라면 무엇이든 됩니다. 압축은 필수입니다. Node.js ws, 브라우저 WebSocket, Python websockets는 기본으로 지원합니다. Python websocket-client와 PHP textalk/websocket은 지원하지 않아 HTTP 426으로 거부됩니다. Go에서는 gorilla/websocket에 EnableCompression을 설정하세요.
피드에 실시간 스코어와 경기 시계도 포함되나요?
네. 연결 URL에 channels=odds,scores,status를 추가하면 배당률과 함께 스코어 변경, 상태 변경, 약 30초 간격의 경기 시계 스냅샷을 받을 수 있습니다. GET /v3/events/live는 같은 스코어와 시계를 REST로 반환합니다.
WebSocket은 어떻게 이용하나요?
유료 플랜을 선택하고 WebSocket 스트리밍을 추가하세요. 요금은 플랜 요금과 같으며, 계정에서 선택한 모든 북메이커를 API 키당 두 개의 연결로 스트리밍합니다. 실시간 배당률과 /v3/odds/updated를 포함한 REST API는 모든 플랜에 포함됩니다.
지금 실시간 배당률을 스트리밍하세요
플랜을 고르고 WebSocket 스트리밍을 추가한 뒤, 연결 하나로 피드에 있는 450개 이상 북메이커의 실시간 배당률을 받아보세요.
언제든 해지 가능 · REST와 WebSocket에서 같은 JSON