본문으로 이동

WebSocket 이벤트

WebSocket으로 명령어 사용, 룰렛 결과 등 채널 이벤트를 실시간으로 받는 방법입니다.

목차

WebSocket으로 연결하면 동의한 채널에서 일어나는 룰렛 결과, 명령어 사용 같은 이벤트를 실시간으로 받습니다. 연결에는 events:subscribe 권한이 필요합니다.

연결

액세스 토큰을 실어 WebSocket으로 연결합니다. 주소는 기본 주소의 https://를 wss://로 바꾸고 /ws를 붙인 것입니다.

WS/api/v1/developer/ws권한: events:subscribe

요청

토큰은 둘 중 한 가지 방법으로 보냅니다. 둘 다 보내면 access_token 쿼리를 씁니다.

이름 위치 타입 필수 설명
access_token query string 둘 중 하나 액세스 토큰. 브라우저의 WebSocket은 헤더를 붙일 수 없으므로 이 방법을 씁니다
Authorization header string 둘 중 하나 Bearer ACCESS_TOKEN. 헤더를 붙일 수 있는 클라이언트에서 씁니다
wss://jamkubot.com/api/v1/developer/ws?access_token=ACCESS_TOKEN

주소에 담긴 토큰은 접속 기록에 남을 수 있습니다. 헤더를 쓸 수 있는 환경이면 헤더로 보내세요.

응답

연결되면 서버가 첫 메시지로 connected를 보냅니다.

필드 타입 설명
type string 항상 connected
streamer_uid string 토큰이 연결된 채널 UID
events array 이 연결로 받을 수 있는 이벤트 타입. 알파벳순이며 연결할 때의 권한 기준입니다
{
  "type": "connected",
  "streamer_uid": "0123456789abcdef0123456789abcdef",
  "events": ["attendance", "command", "optional_feature", "overlay_text", "point_use", "run_roulette", "run_roulette2"]
}

오류

서버는 연결을 받은 뒤 바로 닫는 방식으로 거절합니다. 클라이언트는 연결 종료 코드로 확인합니다.

종료 코드 원인
4401 토큰이 없거나, 유효하지 않거나(만료·갱신으로 바뀜·연결 해제 포함), events:subscribe 권한이 없음

같은 IP 주소에서 1분에 60번 넘게 연결하거나 동시에 32개 넘게 열면 연결을 받지 않습니다. 이때는 종료 코드 없이 핸드셰이크가 HTTP 403 으로 실패하니 잠시 뒤 다시 연결합니다.

메시지 흐름

메시지는 모두 JSON 텍스트입니다. ←는 서버가 보내는 메시지, →는 앱이 보내는 메시지입니다.

← {"type": "connected", "streamer_uid": "0123456789abcdef0123456789abcdef", "events": ["run_roulette", "run_roulette2"]}
← {"streamer_uid": "0123456789abcdef0123456789abcdef", "event": {"type": "run_roulette2", "roulette_name": "간식 룰렛", "...": "..."}}
→ {"type": "ping"}
← {"type": "pong"}
방향 메시지 보내는 때
서버 → 앱 connected 연결 직후 한 번. 필드는 연결 응답을 참고하세요
서버 → 앱 이벤트 프레임 채널에서 받을 수 있는 이벤트가 생길 때마다
앱 → 서버 {"type": "ping"} 연결과 권한을 확인할 때
서버 → 앱 {"type": "pong"} ping을 받았고 토큰이 아직 유효할 때

이벤트 프레임의 형식입니다.

필드 타입 설명
streamer_uid string 이벤트가 생긴 채널 UID
event object 이벤트 본문. event.type으로 종류를 구분합니다. 필드는 이벤트 페이로드를 참고하세요
  • 이벤트 프레임에는 이벤트 ID나 발생 시각이 없습니다.
  • 채팅 한 번으로 이벤트가 여러 개(예: point_use, run_roulette, run_roulette2, command) 발행될 수 있으며, 서로 다른 이벤트의 도착 순서는 보장하지 않습니다.
  • 앱이 보내는 메시지 중 {"type": "ping"}만 처리하고, 다른 JSON 메시지는 무시합니다. 구독할 이벤트를 고르거나 수신을 확인하는 메시지는 없습니다.
  • JSON이 아닌 텍스트나 바이너리 메시지를 보내면 서버가 연결을 끊습니다.

이벤트 종류

받을 수 있는 이벤트는 토큰의 권한 조합으로 정해집니다. 앱에는 봇과 잼쿠봇 서버가 만든 이벤트만 전달되며, 외부 프로그램이 오버레이용 이벤트 채널에 직접 보낸 이벤트는 오지 않습니다.

권한 받는 이벤트
events:subscribe run_roulette, run_roulette2
events:subscribe + commands:read 위 두 이벤트와 command, point_use, attendance, optional_feature, overlay_text

명령어 관련 이벤트에 필요한 권한은 REST 명령어 조회 권한인 commands:read입니다. 명령어 수정 권한 commands:write와 혼동하지 마세요. 권한 목록은 권한 (Scope)에 있습니다.

연결한 뒤 스트리머가 더 좁게 다시 동의하거나 앱의 권한 선언이 줄면 그때부터 빠진 권한의 이벤트는 오지 않습니다. connected.events는 다시 보내지 않습니다. events:subscribe가 빠지면 연결이 4401로 닫힙니다.

이벤트 발행 시점
run_roulette 룰렛이 한 번 돌 때마다. 연차면 회차마다
run_roulette2 룰렛 실행 한 번이 끝났을 때 결과를 모아 한 번
command 커스텀 명령어, !sr 노래 신청, !문방구 뽑기를 실행했을 때
point_use 커스텀 명령어나 !문방구 뽑기로 포인트가 빠지거나 들어왔을 때
attendance 출석 체크에 성공했을 때
optional_feature 포인트가 0인 커스텀 명령어에 출석 체크가 아닌 추가 기능 값이 있을 때
overlay_text 현재 발행하는 곳이 없음

overlay_text는 허용 목록과 connected.events에는 있지만, 지금 잼쿠봇에는 이 타입을 발행하는 기능이 없습니다.

이벤트 페이로드

아래는 이벤트 프레임의 event에 담기는 본문입니다. 시청자가 친 채팅 원문(user_chat, use_command)이 가림 없이 들어 있으니 저장하거나 화면에 띄울 때 주의하세요.

run_roulette

룰렛이 한 번 돌 때마다 발행됩니다. 명령어·후원으로 돌린 룰렛과 대시보드의 룰렛 테스트가 모두 해당하며, 연차면 회차마다 하나씩 옵니다.

필드 타입 설명
type string run_roulette
roulette_name string 룰렛 이름
user_name string 돌린 사람의 닉네임. 익명 후원은 익명의 후원자, 대시보드 테스트는 테스트유저 또는 테스트에서 고른 시청자 닉네임
data array 룰렛 항목 이름 전체(string). 룰렛에 저장된 순서입니다
weights array 항목별 가중치(number). data와 순서가 같습니다
addon string 항상 빈 문자열
resultdata integer 당첨 항목의 data 인덱스(0부터)
{
  "type": "run_roulette",
  "roulette_name": "간식 룰렛",
  "user_name": "시청자닉네임",
  "data": ["치킨", "피자", "꽝"],
  "weights": [20, 30, 50],
  "addon": "",
  "resultdata": 0
}

run_roulette2

룰렛 실행 한 번이 끝나면 그 실행의 결과를 모아 한 번 발행됩니다. 연차 보너스로 더 돈 회차도 들어갑니다. 대시보드 룰렛 테스트는 테스트 한 번마다 하나씩 옵니다.

필드 타입 설명
type string run_roulette2
roulette_name string 룰렛 이름
user_name string 돌린 사람의 닉네임. run_roulette와 같습니다
data array 룰렛 항목 이름 전체(string)
addon string 항상 빈 문자열
resultdata array 당첨 항목의 data 인덱스(integer)를 회차 순서대로. 1회 실행이면 원소가 하나입니다
{
  "type": "run_roulette2",
  "roulette_name": "간식 룰렛",
  "user_name": "시청자닉네임",
  "data": ["치킨", "피자", "꽝"],
  "addon": "",
  "resultdata": [0, 2, 0]
}
참고
테스트도 같은 이벤트로 옵니다

대시보드의 룰렛 테스트와 홈 리모컨의 결과 다시 보기도 run_roulette·run_roulette2를 발행하며, 테스트인지 구분하는 필드는 없습니다. 다시 보기는 원래 기록의 닉네임을 user_name에 씁니다. 룰렛 이벤트에는 시청자 UID도 없으니 누가 돌렸는지 확인하려면 룰렛 내역을 조회하세요.

command

명령어를 실행했을 때 발행됩니다. 커스텀 명령어는 성공했을 때와, 쿨타임·유저별 쿨타임·개인 일일 제한·포인트 부족으로 실패했을 때(success: false) 옵니다. 그 밖의 실패(전체 일일 제한, 방송 중이 아님, 이미 출석함 등)에는 발행하지 않습니다. !sr 노래 신청(신청을 받는 중일 때)과 !문방구 뽑기는 성공과 실패 모두 발행합니다.

필드 타입 설명
type string command
user_chat string 시청자가 친 채팅 원문
used_command string 실행된 명령어. 커스텀 명령어는 등록된 이름(부분 일치 명령어는 %%가 붙은 이름), 노래 신청은 !sr, 문방구 뽑기는 !문방구
user object 실행한 시청자
user.uid string 시청자 UID
user.nickname string 시청자 닉네임
response string 봇의 응답 문구. 실패면 실패 안내 문구입니다. 결과 채팅을 끈 룰렛에 연결된 명령어는 빈 문자열
success boolean 실행에 성공했으면 true
amount integer 걸린 포인트. 커스텀 명령어는 명령어 포인트(연차면 곱한 값, 포인트를 주는 명령어는 음수), !sr은 SR 신청 포인트, !문방구는 성공했을 때의 뽑기 비용(실패면 0)
optional_feature string 명령어의 추가 기능 값. 출석 체크 명령어는 <출석체크>이고 보통은 빈 문자열입니다. 예전 잼쿠봇에서 옮겨 온 명령어는 다른 값일 수 있습니다
overlay_text string 항상 빈 문자열
{
  "type": "command",
  "user_chat": "!간식 3",
  "used_command": "!간식",
  "user": {"uid": "fedcba9876543210fedcba9876543210", "nickname": "시청자닉네임"},
  "response": "시청자닉네임님 치킨(2개), 꽝(1개) 당첨",
  "success": true,
  "amount": 300,
  "optional_feature": "",
  "overlay_text": ""
}

point_use

포인트가 0이 아닌 커스텀 명령어가 포인트를 뺀(또는 준) 직후, 그리고 !문방구 뽑기로 포인트를 썼을 때 발행됩니다. 포인트 설정은 커스텀 명령어를 참고하세요.

필드 타입 설명
type string point_use
user object 시청자. uid, nickname은 command와 같습니다
amount integer 뺀 포인트(양수). 포인트를 주는 명령어는 준 만큼의 음수
use_command string 시청자가 친 채팅 원문
used_command string 실행된 명령어 이름. !문방구 뽑기에는 이 필드가 없습니다
optional_feature string command와 같은 값. !문방구 뽑기는 빈 문자열
overlay_text string 빈 문자열. !문방구 뽑기는 x
{
  "type": "point_use",
  "user": {"uid": "fedcba9876543210fedcba9876543210", "nickname": "시청자닉네임"},
  "amount": 300,
  "use_command": "!간식 3",
  "used_command": "!간식",
  "optional_feature": "",
  "overlay_text": ""
}

attendance

출석 체크 명령어로 출석에 성공했을 때 발행됩니다. 같은 실행의 command 이벤트도 함께 옵니다.

필드 타입 설명
type string attendance
user object 시청자. uid, nickname은 command와 같습니다
attendance_count string 누적 출석 횟수. 숫자를 담은 문자열입니다
streak_count string 연속 출석 횟수. 숫자를 담은 문자열입니다
last_attendance_date string 이번 출석이 기록된 출석일, YYYY-MM-DD. 채널의 출석 기준 시각으로 나눈 날짜입니다
{
  "type": "attendance",
  "user": {"uid": "fedcba9876543210fedcba9876543210", "nickname": "시청자닉네임"},
  "attendance_count": "12",
  "streak_count": "3",
  "last_attendance_date": "2026-10-05"
}

optional_feature

포인트가 0인 커스텀 명령어에 출석 체크가 아닌 추가 기능 값이 있을 때 발행됩니다. 지금 잼쿠봇에서는 이 값을 정할 수 없어서 예전 잼쿠봇에서 옮겨 온 명령어에서만 나옵니다.

필드 타입 설명
type string optional_feature
user object 시청자. uid, nickname은 command와 같습니다
use_command string 시청자가 친 채팅 원문
amount integer 항상 0
optional_feature string 명령어에 저장된 추가 기능 값
overlay_text string 항상 빈 문자열
{
  "type": "optional_feature",
  "user": {"uid": "fedcba9876543210fedcba9876543210", "nickname": "시청자닉네임"},
  "use_command": "!효과음",
  "amount": 0,
  "optional_feature": "효과음",
  "overlay_text": ""
}

연결 유지와 종료

서버가 먼저 보내는 앱 수준의 하트비트는 없습니다. 연결과 권한이 살아 있는지 확인하려면 앱에서 주기적으로(예: 30초마다) {"type": "ping"}을 보내세요.

서버는 메시지를 보내거나 받을 때마다, 그리고 앱이 30초 동안 아무것도 보내지 않으면 그때마다 토큰·동의·권한·만료를 다시 확인합니다. 그 채널에 이벤트가 발행되면 이 연결이 받지 않는 타입이어도 확인합니다. 토큰이 더는 유효하지 않으면 연결을 4401로 닫으므로, 무효가 된 연결은 늦어도 30초 안에 닫힙니다. 예를 들면 다음과 같습니다.

  • 액세스 토큰이 만료됨(발급 후 2시간)
  • 토큰을 갱신함. 갱신하면 이전 토큰 쌍이 바로 무효가 됩니다
  • 같은 채널로 다시 인가해 새 토큰을 받음
  • 스트리머가 연결을 해제함 — 연결 해제
  • 앱이 토큰 폐기를 호출했거나, 회전된 리프레시 토큰이 다시 쓰여 토큰 쌍이 폐기됨
  • 다시 동의하거나 앱의 권한 선언을 줄여 events:subscribe가 빠짐
  • 앱이 삭제됨
종료 코드 뜻 앱이 할 일
4401 토큰이 없거나 더는 유효하지 않음, 또는 events:subscribe 권한 없음 토큰을 갱신하고 새 액세스 토큰으로 다시 연결합니다. 갱신이 invalid_grant로 실패하거나 갱신 응답의 scope에 events:subscribe가 없으면 스트리머에게 다시 동의를 받습니다
4401 앱이 보낸 메시지 하나가 64KiB를 넘었거나, 같은 IP에서 1분에 240개 넘게 보냄 ping 외에는 보낼 메시지가 없으니 주기를 늘리고 다시 연결합니다
4401 서버가 이벤트를 5초 안에 전달하지 못함 수신 처리를 막지 않게 고친 뒤 다시 연결합니다

토큰을 갱신하면 열려 있던 연결은 늦어도 30초 안에 닫히므로, 갱신한 직후 새 토큰으로 다시 연결하세요. 갱신 방법은 토큰 갱신에 있습니다.

끊긴 동안 생긴 이벤트는 다시 보내지 않고, 끊긴 지점부터 이어 받는 기능도 없습니다. 놓친 룰렛 결과는 룰렛 내역으로 확인할 수 있습니다.

이 가이드로 해결이 안 됐나요?

디스코드로 문의하기

명령어, 룰렛, OBS처럼 궁금한 걸 검색해 보세요.