Shoplive Developers
한국어KOEnglishEN日本語JA

Shoplive Web Player SDK 연동 가이드

라이브 커머스 방송을 웹 페이지에 연동하는 방법을 미션으로 나누어 안내합니다. 미션 1 만 하면 방송은 뜹니다 — 나머지는 필요한 것만 고르세요.

빠르게 찾기 왼쪽 사이드바의 검색(또는 Ctrl+K / Cmd+K)으로 이 가이드 안의 섹션을 찾을 수 있습니다. 숏폼 연동은 숏폼 가이드를 참고하세요.
시작하기

개요 & 요구사항

Shoplive Web Player SDK는 스크립트 태그 하나로 라이브 방송 플레이어를 웹 페이지에 연결합니다. 별도 패키지 설치나 빌드 도구 설정이 필요 없습니다.
원하는 위치에 플레이어를 직접 심거나, 링크 하나로 바로 열거나, 다른 페이지에 미리보기를 띄우는 것까지 — 필요한 방식만 골라 적용할 수 있습니다.

바로 시작하기 →

필요한 것

항목확인 방법
Access KeyShoplive Admin — 방송 상세정보 페이지 상단 요약 카드
Campaign KeyShoplive Admin — 방송 상세정보 페이지 상단 요약 카드
Shoplive Admin 방송 상세정보 페이지의 Access key·Campaign key 표시 위치
Shoplive Admin — 방송 상세정보 페이지 상단 요약 카드. Access key·Campaign key 옆 아이콘으로 복사할 수 있습니다. 위 그림에서 값은 가려 두었습니다.

무엇을 구현하고 싶나요?

미션 1 만 끝내면 화면에 방송이 뜹니다. 미션 2~6 은 서로 독립이라 순서와 상관없이, 필요한 것만 골라 적용하면 됩니다 — 전부 해야 하는 목록이 아닙니다.

구현하고 싶은 것어디로
페이지의 특정 위치에 플레이어를 붙인다 · 방송 목록까지 한 번에 노출한다 · 링크 하나로 방송을 연다먼저미션 1 · 방송 노출하기
채팅 이름 표시·입장 제한 등 사용자 정보를 연결한다선택미션 2 · 사용자 정보 연결
쿠폰·상품 클릭, 로그인 필요 등 사용자 상호작용에 고객사 코드를 실행하고 싶다선택미션 3 · 사용자 상호작용 처리하기
방송 페이지가 아닌 다른 페이지에서도 진행 중 방송을 알리고, 공유 링크로 들어오게 한다선택미션 4 · 다른 페이지에도 방송 알리기
떠 있는 플레이어를 고객사 코드에서 조작한다 (쿠폰 지우기·음소거·안내창)선택미션 5 · 플레이어 제어하기
어느 채널로 들어왔고 실제로 구매했는지 집계한다선택미션 6 · 측정 — 유입과 구매
브랜드 좋아요·장바구니·알림 신청·옥션 등 활성화가 필요한 기능을 붙인다선택부록 A · 활성화가 필요한 기능
방송을 보다 상품 페이지로 옮겨도 영상이 끊기지 않게 한다선택부록 B · PIP
SPA · 앱 웹뷰 · RequireJS · 숏폼과 같은 페이지 등 특수한 환경에 맞춘다선택부록 C~F

플레이어 화면의 각 요소를 뭐라고 부르는지는 화면 용어 가이드에 정리돼 있습니다 — 미션을 읽다 removeBanner 가 무엇을 지우는지 헷갈릴 때 돌아오세요.

숏폼도 함께 쓴다면 라이브(shoplive.js)와 숏폼(shoplive-shortform.js)은 별도 스크립트이지만 같은 window.cloud.shoplive 객체를 공유하므로 한 페이지에 함께 로드할 수 있습니다. 사용자 정보는 한 번만 넘기면 양쪽에 반영되고, 두 제품의 프리뷰가 화면 같은 구석에서 겹칠 수 있다는 점만 주의하세요 — 자세한 내용은 부록 F, 숏폼 연동 자체는 숏폼 가이드를 보세요.
시작하기

빠른 시작

가장 짧은 경로로 화면에 플레이어를 띄우는 예시입니다. 세부 옵션은 아래 미션 1부터 참고하세요.

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });
</script>

<div id="shoplive-player"></div>
<script>
  cloud.shoplive.setPlayer("shoplive-player", { campaignKey: "YOUR_CAMPAIGN_KEY" });
</script>

cloud.shoplive 는 SDK 가 만들어 두는 전역 객체입니다. 위 스크립트가 로드되면 window.cloud.shoplive 에 initPlugin · setPlayer 같은 함수가 붙습니다. 그래서 import 나 번들러 설정 없이 어느 스크립트에서든 cloud.shoplive.… 로 바로 호출할 수 있습니다 — 단 SDK 스크립트 태그보다 뒤에서 호출해야 합니다. 쓸 수 있는 함수 전체는 함수 색인에 있습니다.

다음 단계화면 용어 가이드 · 미션 1부터 시작 →

시작하기

화면 용어 가이드

플레이어 화면의 각 요소를 부르는 이름을 정리한 것입니다. 이후 미션 · 이벤트 사전 · 명령 사전에서 같은 용어와 같은 번호를 씁니다. removeBanner 가 무엇을 지우는 명령인지 헷갈릴 때 이 표로 돌아오세요.

이 이미지는 모바일 SDK 캡처입니다 웹 플러그인은 같은 플레이어 UI를 그대로 띄우므로 요소 이름과 위치가 동일합니다. 다만 아이폰 상태 표시줄 같은 앱 껍데기는 웹에 없고, ⑫ 최소화(PIP) 버튼도 웹에는 없습니다 — 자세한 차이는 표 아래에 적었습니다.

플레이어 화면

라이브 플레이어 화면의 각 요소에 1~12 번호를 붙인 그림
번호요소노출 조건웹에서 관련된 것
①영상 영역상시playVideo · pauseVideo · videoRefresh (명령 사전)
②브랜드 로고상시Shoplive Admin 의 캠페인 설정 — 코드로 바꾸지 않습니다
③캠페인 제목상시Shoplive Admin 의 캠페인 설정
④LIVE 배지 · 시청자 수 · 좋아요 수LIVE 전용시청자 수·좋아요 수는 ON_CHANGED_VIEWER_STAT, 방송 상태(시작·종료)는 ON_CAMPAIGN_STATUS_CHANGED 로 받을 수 있습니다 (미션 3 · 알림만 받는 이벤트). 표시 자체는 ui.viewerCount · ui.likeCount 로 켜고 끌 수만 있습니다
⑤채팅 오버레이LIVE 전용영상 위로 흐르는 채팅. 웹에서 제어하는 API 는 없습니다
⑥상품 카드 (좌하단 · NOW)상시클릭 시 CLICK_PRODUCT (미션 3) · 목록 닫기는 dismissGoodsList
⑦우측 아이콘 열 (♡ · ? · 💬)상시좋아요 · 문의 · 채팅 열기. 열 전체를 hideControls · showControls 로 감추고 보입니다
⑧음소거 버튼상시mute · unmute. 자동재생 때문에 처음에는 음소거로 시작합니다
⑨쿠폰 버블운영자가 발행했을 때클릭하면 Admin 에서 고른 클릭 동작이 그대로 이벤트로 옵니다(미션 3 · 팝업) · 제거는 removeCoupon
⑩공지 pill운영자가 발행했을 때공지도 눌립니다 — ⑨ · ⑪ 과 같은 클릭 동작 설정을 따릅니다(미션 3 · 팝업) · 제거는 removeNotice
⑪이벤트 배너 · 팝업운영자가 발행했을 때Admin 에서 고른 클릭 동작이 곧 이벤트 이름입니다 — LINK · LINK_NEW_WINDOW · DOWNLOAD_COUPON · CUSTOM_ACTION (미션 3 · 팝업). ⑨ 쿠폰 · ⑩ 공지도 같은 방식입니다 · 제거는 removeBanner
⑫상단 오른쪽 버튼상시웹은 구성이 다릅니다 — 아래 설명 참고

모바일과 다른 곳

그림웹 플러그인
⑫ 최소화(in-App PIP) 버튼없습니다. 화면 한쪽에 작게 띄우는 것은 별개 기능인 미니 프리뷰(setLiveMiniPreview)이고, 플레이어 안의 버튼이 아니라 고객사가 직접 호출합니다
‹ 뒤로가기있습니다. 누르면 CLICK_BACK_BUTTON — 기본 동작은 history.back() 이라 페이지가 뒤로 갑니다. 모달로 띄웠다면 닫기 처리를 함께 보세요
공유 아이콘있습니다. CLICK_SHARE_BTN · 내보낼 URL 은 shareUrl 로 지정합니다. 숨기려면 hideShareButton
아이폰 상태 표시줄 · 홈 인디케이터앱 화면일 뿐이라 웹에는 없습니다. 웹 플레이어는 고객사가 잡아 준 div 크기(또는 모달)에 맞춰 그려집니다

플레이어 안의 UI 는 대부분 Shoplive Admin 의 캠페인 설정으로 켜고 끕니다. 코드에서 바꿀 수 있는 것은 미션 1 · 화면에 무엇을 보일지에 모아 두었습니다.

이전 ← 빠른 시작다음 → 미션 1 · 방송 노출하기

미션

미션 1 · 방송 노출하기

목표웹 페이지에 방송을 노출한다
사전 준비Access Key · Campaign Key (Shoplive Admin — 방송 상세정보 페이지 상단 요약 카드)

노출 방식은 세 가지입니다. 하나만 고르면 되고, 셋 다 같은 initPlugin() 위에서 동작합니다.

구분방법 A · 플레이어만방법 B · 링크로 열기방법 C · 라이브 페이지 템플릿
화면에 나오는 것플레이어 하나플레이어 하나 (전체 화면)대표 방송 + 방송 목록까지 한 번에
화면 위치·크기 커스텀자유롭게 가능불가 (shoplive.show 고정 레이아웃)영역 단위로 선택 (플레이어만 / 목록만)
레이아웃을 고객사가 짜야 하나예 — 컨테이너와 주변 UI 를 직접 구성아니오아니오 — Shoplive 템플릿 사용
사용자 인증 설정코드(initPlugin) 안에서 설정URL 의 tk 파라미터로 설정코드(initPlugin) 안에서 설정
쿠폰·상품 클릭 등 커스텀 로직(messageCallback)가능불가가능
PIP (상품 페이지로 옮겨도 계속 보기)가능불가 — 고객사 페이지에 SDK 가 없습니다가능
대표 활용처이벤트 전용 페이지, 특정 캠페인 전용 기획전광고 소재 랜딩상설 라이브 코너 — 방송이 계속 바뀌는 페이지

방법 A플레이어 직접 띄우기

페이지를 직접 만들 수 있는 상황이라면 이 방법을 사용합니다. 원하는 위치에 컨테이너를 두고 setPlayer를 호출합니다.

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });
</script>

<div id="shoplive-player"></div>
<script>
  cloud.shoplive.setPlayer("shoplive-player", {
    campaignKey: "YOUR_CAMPAIGN_KEY"
  });
</script>

플레이어는 컨테이너 div 안을 채우므로, 컨테이너의 크기·위치는 페이지 CSS 로 직접 잡습니다. 방송이 끝나 CLOSED 가 되어도 campaignKey 는 그대로 두면 됩니다 — 같은 키로 다시보기가 재생됩니다.

방법 A 실제 화면. 방송 하나를 알리려고 고객사가 직접 만든 라이브 기획전 페이지입니다. 방송 제목·일정·혜택 배지, 오른쪽의 방송 상품 목록, 아래의 방송 포인트와 유의사항까지 전부 고객사가 짠 레이아웃이고, SDK 가 채우는 것은 왼쪽 div 하나뿐입니다. 스크롤해도 플레이어가 따라붙는 것은 그 div 에 position:sticky 를 준 것으로, 페이지 CSS 로 자유롭게 잡을 수 있다는 뜻입니다. 영상 속 쇼핑몰은 촬영용 예시이며 문구는 영문입니다.

화면에 무엇을 보일지 선택

옵션기본값설명
ui.viewerCounttrue시청자 수 표시
ui.likeCounttrue좋아요 수 표시
ui.backButtonfalse뒤로가기 버튼 표시. 누르면 CLICK_BACK_BUTTON 이 발생하고 기본 동작은 history.back() (미션 3)
ui.shareButtonfalse공유 버튼 표시 (공유 URL)
cloud.shoplive.setPlayer("shoplive-player", {
  campaignKey: "YOUR_CAMPAIGN_KEY",
  ui: { viewerCount: true, likeCount: true, backButton: true, shareButton: true }
});

채팅창·상품 목록 등 플레이어 안쪽 구성은 계정 설정과 테마가 정합니다. 위 네 가지 외의 요소를 바꿔야 한다면 Shoplive 담당자에게 문의하세요.

공유 버튼이 내보내는 URL 선택

공유 버튼을 켰다면, 공유되는 주소를 initPlugin()의 shareUrl 로 지정할 수 있습니다. 지정하지 않으면 현재 페이지 주소가 쓰입니다. 공유 동작 자체를 바꾸는 방법과 URL 구성 규칙은 미션 3을 참고하세요.

cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  shareUrl: 'https://my-shop.com/live'   // 선택 — 생략하면 현재 페이지 주소
});

항상 전체 화면으로 선택

cloud.shoplive.setPlayer("shoplive-player", {
  campaignKey: "YOUR_CAMPAIGN_KEY",
  isFullScreen: true
});
PC·모바일 차이 — isFullScreen엔 viewport meta 태그 필수 isFullScreen: true를 사용하면 SDK가 페이지의 <meta name="viewport"> 태그를 찾아 모바일 핀치줌을 막도록 값을 조정합니다. 이 태그가 페이지에 없으면 에러 없이 그냥 무시되니, 아래처럼 미리 넣어두세요.
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover, shrink-to-fit=no">

그 밖의 옵션 선택

특정 상황에서만 쓰는 값들입니다. 평소에는 지정하지 않아도 됩니다.

옵션타입설명
hideControlsbooleantrue 면 플레이어의 컨트롤 UI 를 숨긴 채로 시작합니다. 시작 후에는 플레이어 제어의 showControls / hideControls 명령으로도 바꿀 수 있습니다
playerAliasstring이 플레이어에 붙이는 별명. 한 페이지에 플레이어가 여러 개일 때 getPlayer('별명') 으로 정확히 집어내기 위해 씁니다 (미션 5). 지정하지 않으면 campaignKey 로 찾습니다
closePlayerAfterCampaignEndnumber (ms)방송이 끝난 뒤 이 시간만큼 지나면 CLOSE_PLAYER 이벤트를 보냅니다. 방송이 끝났을 때 페이지를 정리하거나 다른 화면으로 보내는 신호로 쓰는 값입니다 — SDK 가 이 이벤트로 플레이어를 닫지는 않으므로 실제로 치우려면 destroyPlayer() 를 직접 불러야 합니다.
지정하지 않으면 방송 종료 30초 뒤 플레이어가 스스로 새로고침해 종료 화면으로 정리됩니다
chatFilterPatternsstring[]정규식 문자열 배열. 시청자가 보내려는 채팅이 하나라도 일치하면 전송하지 않고 ON_FILTERED_CHAT 이벤트를 대신 발생시킵니다. 자체 금칙어 정책을 코드로 얹을 때 씁니다
passcodestring리허설 방송을 미리 보기 위한 입장 코드. 리허설로 설정된 방송은 ONAIR 가 되기 전까지 이 값이 있어야 입장됩니다
brandobject브랜드 좋아요 기능을 쓸 때 초기 상태를 넘깁니다 — { identifier, favorite } (부록 A · 브랜드 좋아요)

다시 그리기 · 정리하기

같은 컨테이너에 setPlayer 를 다시 호출하면 기존 플레이어를 정리하고 새로 만듭니다. 다른 방송으로 갈아 끼울 때 destroy 를 먼저 부를 필요가 없습니다.

반대로 컨테이너 자체가 화면에서 사라질 때(SPA 에서 컴포넌트 언마운트 등)는 SDK 가 이를 감지하지 못해 플레이어가 내부적으로 살아 있게 됩니다. 컨테이너를 제거하기 전에 destroyPlayer 를 먼저 호출하세요.

cloud.shoplive.destroyPlayer("shoplive-player");

방법 B링크로 바로 열기

페이지를 새로 만들기 어려운 상황이라면, 별도 페이지 구성 없이 Shoplive가 제공하는 shoplive.show 페이지를 URL 파라미터만으로 호출합니다. 단, SDK는 shoplive.show 페이지 안에서만 동작하고 이 URL을 여는 고객사 웹페이지에는 로드되지 않으므로, initPlugin()을 직접 호출할 수 없습니다. 사용자 상호작용 처리(미션 3)가 필요하면 방법 A 또는 C를 사용해야 합니다.

https://www.shoplive.show/v1/player.html?ak={access_key}&ck={campaign_key}&tk={사용자정보}
파라미터필수설명
ak필수Access Key
ck필수Campaign Key
tk선택사용자 정보 — URL-encode 한 userObject JSON 또는 JWT(미션 2). 게스트 시청이면 생략
userId · userName
gender · age · userScore
선택tk 대신 쓰는 간이 방식. 이 다섯 개만 지원하며 tk 를 함께 주면 tk 가 우선합니다 (미션 2)
backButton선택1(표시) / 0(숨김)
shareButton선택1(표시) / 0(숨김)

공유 버튼 클릭 시 전달할 페이지 URL을 지정하는 shareUrl 옵션(방법 A 참고)은 initPlugin()을 직접 호출해야 적용되므로, 방법 B에는 사용할 수 없습니다. 방법 B에서는 shareButton=0으로 공유 버튼 자체를 숨기는 것만 가능합니다.

방법 B 실제 화면. 고객사 페이지에는 링크 하나만 걸어 둡니다. 보러가기 버튼을 누르면 Shoplive 가 제공하는 페이지가 새 창으로 열리고 거기서 방송이 재생됩니다. 열린 화면에는 고객사 헤더도 상품도 없고 주소도 고객사 도메인이 아닙니다 — 그래서 그 페이지에는 고객사 코드를 넣을 자리가 없고, messageCallback 도 등록할 수 없습니다.

방법 C라이브 페이지 템플릿 — 플레이어와 방송 목록을 한 번에

플레이어 하나가 아니라 라이브 코너 페이지 전체가 필요할 때 씁니다. 빈 div 하나에 setOverall 을 호출하면 그 안에 대표 방송(진행 중이거나 가장 최근 방송) + 방송 목록이 Shoplive 템플릿으로 그려집니다. 어떤 방송을 띄울지 코드로 정하지 않아도 되므로, 방송이 계속 바뀌는 상설 코너에 적합합니다.

<div id="shoplive-overall-area"></div>
<script>
  cloud.shoplive.setOverall('shoplive-overall-area');
</script>
방법 C 실제 화면. 고객사 쇼핑몰의 "라이브" 코너를 가정한 화면입니다. 헤더·페이지 제목·푸터는 고객사가 만든 것이고, 그 사이의 대표 방송과 방송 목록, 목록을 눌렀을 때 열리는 모달 플레이어까지가 setOverall 이 그린 영역입니다. 이 화면은 두 영역을 따로 배치하려고 div 를 둘로 나눠 featuredOnly · campaignListsOnly 로 각각 호출했습니다(어느 영역만 보일지).

목록의 방송을 누르면 SDK 가 알아서 모달 플레이어를 엽니다. 이 동작을 직접 처리하고 싶다면 ON_CLICK_CAMPAIGN_LIST_ITEM 을 등록하세요 (미션 3).

어느 영역만 보일지 선택

하려는 것옵션
기본 — 대표 방송 + 목록옵션 없음
대표 방송(플레이어) 영역만{ featuredOnly: true }
방송 목록 영역만{ campaignListsOnly: true }
대표 방송을 특정 방송으로 고정{ featuredOnly: true, featured: { fixedCampaignKey: 'YOUR_CAMPAIGN_KEY' } }

두 영역을 서로 떨어진 위치에 두고 싶다면, div 를 두 개 만들어 하나에는 featuredOnly, 다른 하나에는 campaignListsOnly 로 각각 호출하면 됩니다.

목록을 어떻게 구성할지 선택

cloud.shoplive.setOverall('shoplive-overall-area', {
  campaignListsOnly: true,
  campaignStatus: ['ONAIR', 'CLOSED'],   // 이 순서대로 정렬됩니다
  tags: ['sale', 'new'],
  tagSearchOperator: 'OR',               // 'OR' 하나라도 일치 · 'AND' 모두 일치
  displayTags: ['sale', 'new', 'best'],  // 목록 위에 필터 탭으로 노출
  campaignList: { layout: 'HORIZONTAL' } // 'GRID'(기본) | 'HORIZONTAL'
});
옵션기본값설명
campaignStatus전체'READY'(예정) · 'ONAIR'(진행 중) · 'CLOSED'(종료·다시보기). 문자열 하나 또는 배열. 배열로 주면 적은 순서대로 정렬되고, 같은 상태 안에서는 방송 예정 시각 최신순입니다. 이 값은 campaignListsOnly: true 일 때만 적용됩니다 — 대표 방송과 목록을 한 div 에 함께 그리는 기본 모드에서는 진행 중 · 예정 · 다시보기 세 섹션이 항상 나옵니다
tags / tagSearchOperator—방송에 붙인 태그로 필터. 여러 개일 때 'OR' / 'AND' 선택
displayTags—목록 위에 필터 탭으로 노출할 태그. 시청자가 눌러 좁혀 볼 수 있습니다
skus / goodsIds—특정 상품이 포함된 방송만 — skus 는 고객사 상품코드, goodsIds 는 Shoplive 내부 상품 ID
campaignList.layout'GRID''GRID' 격자 · 'HORIZONTAL' 한 줄 가로 스크롤
campaignList.type—'SECTION' 을 주면 상태별 섹션으로 나눠 표시합니다
hasMoreButtontrue목록 아래 더보기 버튼. false 로 두면 'GRID' 에서는 무한 스크롤로 바뀝니다
ascending상태에 따라정렬 방향. 지정하지 않으면 campaignStatus 가 'READY' 문자열 하나일 때만 오름차순(빠른 방송부터)이고, 그 밖에는 내림차순입니다
sectionTitles기본 문구목록 섹션 제목을 고객사 서비스 용어로 바꿉니다 — { onair, ready, closed, all }
cloud.shoplive.setOverall('shoplive-overall-area', {
  sectionTitles: {
    onair:  '지금 라이브',
    ready:  '방송 예정',
    closed: '지난 방송',
    all:    '전체 방송'
  }
});

정리하기

SPA 에서 페이지를 벗어날 때는 destroyOverall 을 호출합니다. 두 번째 인자를 true 로 주면 내부에 남아 있는 플레이어까지 함께 정리합니다.

cloud.shoplive.destroyOverall('shoplive-overall-area', true);

모달로 띄우기 선택

페이지에 영역을 만들지 않고, 버튼을 눌렀을 때 화면 위에 모달로 방송을 띄우는 방식입니다. 배너나 버튼에서 바로 방송을 열 때 씁니다.

<button id="open-live">라이브 보기</button>
<script>
  document.getElementById('open-live').addEventListener('click', function () {
    cloud.shoplive.showFeaturedPlayerModal({ campaignKey: 'YOUR_CAMPAIGN_KEY' });
  });

  // 코드로 닫기
  // cloud.shoplive.dismissLayerModalPlayer();
</script>
뒤로가기를 누르면 모달만 닫힙니다 모달이 열리면 SDK 가 URL 에 방송 키를 기록해 두기 때문에, 모바일에서 뒤로가기를 눌러도 페이지를 떠나지 않고 모달만 닫힙니다. 이 동작이 자체 라우터와 충돌한다면 showFeaturedPlayerModal({ campaignKey, disableHistoryHook: true }) 로 끌 수 있습니다 — 대신 뒤로가기가 이전 페이지로 나갑니다.

완료 확인

  • (방법 A) 지정한 div 위치에 플레이어가 렌더링되고, 방송이 ONAIR 상태면 영상 자동 재생을 시도한다 (브라우저 자동재생 정책에 따라 제한될 수 있음)
  • (방법 B) URL만으로 별도 페이지 구성 없이 플레이어가 열린다
  • (방법 C) 지정한 div 안에 대표 방송과 방송 목록이 그려지고, 목록의 방송을 누르면 모달이 열린다
  • 공유 버튼을 켰다면, 클릭 시 shareUrl을 지정했으면 그 페이지로, 지정하지 않았으면 현재 페이지로 공유된다

이전 ← 화면 용어 가이드다음 → 미션 2 · 사용자 정보 연결

미션

미션 2 · 사용자 정보 연결

목표채팅 이름 표시, 팝업 클릭 로그, 입장 제한 등에 쓸 시청자 정보를 연결한다
사전 준비미션 1(방법 A 또는 B) 완료 · (JWT 인증 시) 백엔드 서명 로직 준비 (5단계)
setUserObject()·setUserJWT() 는 사용자 정보를 넘기는 것이지, 사용자를 인증하는 것이 아닙니다 로그인(아이디·비밀번호, 소셜 로그인 등)은 전적으로 고객사 사이트가 합니다. 이 함수들은 지금 시청자가 누구인지 Shoplive 에 알려 주기만 해서, 채팅·시청·통계가 그 회원 기준으로 남게 합니다. Shoplive 는 사용자의 자격 증명을 확인하지 않습니다. JWT 인증은 그 정보가 고객사 서버가 발급했고 바뀌지 않았는지(서명)를 Shoplive 가 확인할 수 있고, 간편 인증(userObject)은 그 확인 없이 정보를 그대로 넘깁니다.

1단계 · 로그인 상태에 따라 고르기 — 권장 방식

사용자권장이유
비로그인아무것도 호출하지 않음 (게스트)사용자 정보를 전달할 필요가 없습니다. 게스트 채팅 허용 여부는 Admin에서 설정하며, 비허용 시 채팅 입력창이 비활성화되고 "채팅에 참여하기" 문구가 표시됩니다. 게스트를 구분할 식별값이 필요하면 게스트 uid 받기를 참고하세요
로그인 (권장)setUserJWT() — 서버가 서명한 JWT (JWT 인증)위변조할 수 없어 userScore·userType 기준 입장 제한처럼 권한을 가르는 데 쓸 수 있습니다
로그인 (백엔드 작업이 어려울 때)setUserObject() — id·이름 등을 직접 전달 (간편 인증)클라이언트 값을 서명 없이 그대로 넘깁니다. 식별·통계에는 충분하지만 권한 검증에는 쓰지 마세요
로그인 (Cafe24·WISA·고도몰·Shopify 쇼핑몰)플랫폼 자동 연동쇼핑몰이 이미 아는 로그인 상태를 SDK가 읽어 갑니다. 위 두 방식과 택일이며, 사용자 정보를 직접 넘기면 그 값이 우선합니다
로그아웃 · 사용자 정보 초기화cloud.shoplive.logout() · 또는 setUserObject(null) / setUserJWT(null)설정한 사용자 정보를 지우고 게스트 상태로 돌아갑니다
권장 흐름 페이지 진입(비로그인) → 아무것도 하지 않음 → 로그인 상태: 페이지를 열 때마다(SPA 라면 setUserJWT() 를 부르기 직전마다) 백엔드에서 새로 발급한 JWT 를 받아 initPlugin({ userJWT }) 또는 setUserJWT() → 로그아웃 시 logout()(또는 null 전달)로 사용자 정보 초기화. 방법 B(링크로 열기)는 같은 JWT 를 tk 파라미터로 넘깁니다(2단계).

2단계 · 선택한 방식으로 호출

사용자 정보를 initPlugin() 시점에 이미 알고 있다면 initPlugin() 호출에 바로 포함시키고, 로그인 여부가 나중에 확정되는 상황(SPA 등)이라면 setUserObject()/setUserJWT()로 나중에 따로 호출합니다. 간편 인증은 userId 외 필드가 전부 선택사항이고, JWT는 필수 필드가 따로 있습니다(5단계 참고). 게스트는 둘 다 호출하지 않으면 됩니다.

// 간편 인증 — userId만 필수, 나머지는 선택
const userObject = {
  userId: "",     // 필수 — 로그인한 사용자의 고유 ID. 빈 값이면 비로그인(게스트) 상태로 처리됨
  userName: "",   // 선택 — 채팅에 표시될 이름. 미지정 시 캠페인 설정에 따라 입력 강제/임의 생성. 금칙어는 *로 대체되어 표시
  gender: "",     // 선택 — "m" | "f" | 미지정 시 빈 값
  age: "",        // 선택 — 통계에는 5세 단위로 그룹화되어 표시됨
  userScore: "",  // 선택 — -100~100 정수. 이벤트 추첨 제한 등의 기준으로 활용 가능
  userType: "",   // 선택 — 입장 제한(Entry Type) 설정 시 참조되는 값
  profile: "",    // 선택 — 채팅 목록 프로필 이미지 URL, 권장 64x64px
  custom: "",     // 선택 — 자유 형식 데이터, 최대 1KB
  canBid: true    // 선택 — 경매 입찰 자격 (부록 A · 옥션)
};

// 방법 1 — initPlugin() 호출에 바로 포함
cloud.shoplive.initPlugin({ accessKey: "YOUR_ACCESS_KEY", userObject });

// 방법 2 — initPlugin() 이후 별도 호출 (로그인 정보가 나중에 확정될 때)
cloud.shoplive.initPlugin({ accessKey: "YOUR_ACCESS_KEY" });
cloud.shoplive.setUserObject(userObject);


// JWT 인증도 방식은 동일 — 아래 5단계에서 만든 토큰을 전달
cloud.shoplive.initPlugin({ accessKey: "YOUR_ACCESS_KEY", userJWT: "USER_JWT" });
// 또는
cloud.shoplive.setUserJWT("USER_JWT");


// 로그아웃 · 사용자 정보 초기화 — 설정한 사용자 정보를 지우고 게스트 상태로 돌아갑니다
cloud.shoplive.logout();
// 또는 넘긴 값을 null 로: cloud.shoplive.setUserObject(null) / cloud.shoplive.setUserJWT(null)
방법 B(링크로 열기)라면 — URL의 tk 파라미터로 전달 방법 B는 initPlugin() 호출 자체가 없으므로, 위 userObject를 JSON 문자열로 만들어 URL-encode한 값을 tk 파라미터에 넣습니다. JWT는 인코딩 없이 토큰 문자열을 그대로 넣습니다.
// 간편 인증
https://www.shoplive.show/v1/player.html?ak=YOUR_ACCESS_KEY&ck=YOUR_CAMPAIGN_KEY&tk=encodeURIComponent(JSON.stringify(userObject))

// JWT
https://www.shoplive.show/v1/player.html?ak=YOUR_ACCESS_KEY&ck=YOUR_CAMPAIGN_KEY&tk=USER_JWT

필드가 몇 개 안 된다면 개별 파라미터로도 됩니다. userId·userName·gender·age·userScore 는 쿼리 파라미터로 직접 넘길 수 있어, JSON 을 만들어 인코딩할 필요가 없습니다. 다만 userType·profile·custom·canBid 와 JWT 는 이 방식으로 전달되지 않으니 tk 를 쓰세요. tk 를 함께 주면 tk 가 우선합니다.

https://www.shoplive.show/v1/player.html?ak=YOUR_ACCESS_KEY&ck=YOUR_CAMPAIGN_KEY&userId=MEMBER_ID&userName=NICKNAME

3단계 · 쇼핑몰 플랫폼을 쓴다면 — 자동 연동 선택

Cafe24·WISA·고도몰·Shopify로 만든 쇼핑몰이라면, setUserObject()를 직접 부르는 대신 initPlugin()에 platform만 넘겨 쇼핑몰의 로그인 상태를 읽어 가게 할 수 있습니다. 2단계의 대안이지 2단계에 더하는 작업이 아닙니다.

사용자 정보를 직접 넘기면 자동 연동은 동작하지 않습니다 SDK는 userObject·userJWT가 없을 때만 플랫폼 자동 연동을 시도합니다. initPlugin()에 사용자 정보를 함께 넘겼다면 그 값이 쓰이고 platform은 무시됩니다. 둘 중 하나만 쓰세요.
경매를 쓴다면 — 자동 연동된 사용자는 canBid 가 켜집니다 플랫폼 자동 연동으로 만들어진 사용자 정보에는 canBid: true 가 항상 붙습니다. 즉 쇼핑몰에 로그인한 사람이면 누구나 옥션에 입찰할 수 있습니다. 등급·구매 이력 등으로 입찰 자격을 가려야 한다면 자동 연동 대신 2단계처럼 setUserObject()를 직접 호출해 canBid 를 계산해 넘기거나, CAN_BID_REQUIRED 로 입찰 시점에 판단하세요.
platform 값어떻게 읽어 가나고객사가 페이지에 준비할 것
'CAFE24'Cafe24 스킨의 로그인 상태 모듈이 그려 주는 회원 정보를 읽습니다. 값이 늦게 채워지는 스킨이면 채워질 때까지 기다렸다 연결합니다스킨에 로그인 상태 모듈이 없다면 아래 A를 넣습니다
'WISA' · 'GODOMALL'페이지에 있는 약속된 id의 엘리먼트에서 회원 정보를 읽습니다아래 B의 두 엘리먼트를 직접 출력해야 합니다
'SHOPIFY'위와 같은 방식으로 읽고, 더해서 플레이어 안 장바구니 기능이 켜집니다B + 장바구니 연동 (부록 A · 장바구니 담기)

어느 경우든 공통 레이아웃(모든 페이지가 공유하는 머리말 영역)에 넣어, 어떤 페이지로 들어와도 로그인 상태가 잡히게 하는 것이 좋습니다.

<!-- 1. SDK 로드 -->
<script src="https://static.shoplive.cloud/shoplive.js"></script>

<!-- A. Cafe24 — 스킨에 로그인 상태 모듈이 없을 때만 추가합니다.
       {$id} {$name} 은 Cafe24 가 로그인 회원의 아이디·이름으로 바꿔 줍니다.
       이 모듈이 렌더되면 .xans-member-var-id / .xans-member-var-name 이 만들어지고,
       SDK 는 그 두 클래스를 찾아 읽습니다 -->
<div module="Layout_stateLogon" style="display:none;">{$id} {$name}</div>

<!-- B. WISA · 고도몰 · Shopify — 회원 정보를 이 id 로 출력합니다.
       중괄호 안은 쇼핑몰 솔루션이 제공하는 회원 변수로 바꿔 넣으세요
         · 고도몰 : {=gSess.memId} / {=gSess.memNm}
         · WISA   : {{$회원아이디}} / {{$회원명}}
       비로그인 상태에서는 값이 비므로 게스트로 동작합니다 -->
<div style="display:none;">
  <span id="cloud-shoplive-user-id">MEMBER_ID</span>
  <span id="cloud-shoplive-user-name">NICKNAME</span>
</div>

<script>
  // 2. platform 만 넘깁니다 — setUserObject 는 호출하지 않습니다
  cloud.shoplive.initPlugin({
    accessKey: 'YOUR_ACCESS_KEY',
    platform: 'CAFE24'      // 'CAFE24' | 'WISA' | 'GODOMALL' | 'SHOPIFY'
  });
</script>
visibility: hidden으로 숨기면 값이 비어 버립니다 회원 정보 엘리먼트를 화면에 보이지 않게 처리할 때, display: none이나 화면 밖으로 밀어내는 방식은 값을 읽는 데 문제가 없지만 visibility: hidden은 빈 문자열로 읽혀 게스트가 됩니다. 또 Cafe24를 제외한 나머지는 초기화 시점에 한 번만 읽으므로, 회원 정보를 스크립트로 나중에 채운다면 initPlugin()을 그 뒤에 호출하세요.

연결됐는지 확인하려면. 로그인 상태로 방송에 들어가 채팅에 표시되는 이름을 보면 됩니다. Cafe24에서 회원 정보 영역(.xans-member-var-id·.xans-member-var-name)을 찾지 못하면 콘솔에 Failed - Get Cafe24 account가 남고, 나머지 플랫폼은 엘리먼트가 없으면 조용히 게스트로 남습니다. 자동 연동이 여의치 않으면 2단계처럼 setUserObject()를 직접 호출하면 됩니다.

각 쇼핑몰 관리자 화면에서 코드를 어디에 넣는지는 부록 G · 쇼핑몰 플랫폼별 설치 위치를 참고하세요.

4단계 · ID 해싱 — 회원 ID 원문을 노출하지 않기

내부 회원 ID를 원문 그대로 넘기지 않으려면, 고객사 서버에서 해싱한 값을 userId로 사용할 수 있습니다. 마스킹과 달리 Shoplive는 원본 ID를 알 수 없지만, 고객사는 자체 해싱 로직(동일 알고리즘·salt)으로 같은 값을 다시 계산해 원래 사용자와 대응시킬 수 있습니다. 같은 사용자가 항상 같은 값이 나오도록(통계·재방문 식별) 일관된 방식으로 처리하면 됩니다.

// Node.js — 고객사 서버에서 해싱합니다 (salt·알고리즘은 항상 같게)
const crypto = require('crypto');

function hashUserId(raw) {
  return crypto.createHash('sha256')
    .update(raw + 'YOUR_SALT')
    .digest('hex');
}

const userId = hashUserId('MEMBER_ID');   // userObject.userId 나 JWT 의 userId 로 씁니다
보안 참고 해싱은 원문 은닉일 뿐 인증이 아닙니다. 권한 검증이 필요하면 반드시 서버 서명 JWT(setUserJWT())를 사용하세요. salt 정책은 Shoplive 팀과 합의된 값을 권장합니다.

5단계 · JWT 인증용 토큰 만들기 (고객사 백엔드)

JWT 인증에 쓸 JWT 는 Shoplive 가 발급한 Secret Key 로 고객사 백엔드가 서명합니다. 페이지는 백엔드에서 받은 JWT 를 initPlugin({ userJWT }) 나 setUserJWT() 로 넘기기만 합니다(방법 B 는 tk 파라미터).

  1. Shoplive 담당자에게서 Secret Key 를 받습니다 — base64 로 인코딩된 문자열입니다
  2. 로그인한 사용자 정보로 아래 payload 를 만들고 HS256 으로 서명합니다
  3. 페이지가 요청할 때마다(setUserJWT() 를 부르기 직전마다) JWT 를 새로 발급해 내려 줍니다. 브라우저에 JWT 를 저장해 두고 다시 쓰지 않습니다
JWT 는 매번 새로 만들어 씁니다 브라우저(localStorage·쿠키 등)에 JWT 를 캐시하거나 예전 토큰을 다시 쓰지 마세요. 지난 발급 뒤로 사용자 정보(이름·등급·유형)가 바뀌었을 수 있고, 유출된 토큰은 exp 까지 계속 쓸 수 있습니다. 페이지를 열 때마다, 또는 setUserJWT() 를 부를 때마다 백엔드에서 새 JWT 를 받게 하세요.
필드 이름 차이 주의 JWT의 payload(클레임) 필드는 간편 인증과 이름이 다른 것이 있습니다 — 채팅 표시 이름은 userName이 아니라 name이고, 만료 시각 exp가 필수입니다. exp는 JWT 라이브러리가 제공하는 만료 설정 API(아래 예시의 setExpiration · expiresIn)로 지정하세요 — 라이브러리가 exp를 채워 줍니다.
claim필수설명예
exp필수만료 시각 (Unix 초). 라이브러리의 만료 설정으로 채웁니다1516239022
userId필수로그인 사용자의 유일성을 보장할 수 있는 값. 해시 값을 써도 됩니다(4단계). 빈 값이면 비로그인(게스트) 상태로 처리됨MEMBER_ID
iat선택발급 시각 (Unix 초)1516195822
name선택채팅창에 표시될 이름 (간편 인증의 userName에 대응). 금칙어는 *로 대체되어 표시NICKNAME
gender선택성별 — m · f · 빈 값(미지정)f
age선택나이. 통계에는 5세 단위로 그룹화되어 표시됨27
userScore선택사용자 등급 (-100 ~ 100). 이벤트 추첨 제한 등의 기준10
userType선택입장 제한(Entry Type) 설정 시 참조되는 값VIP
profile선택채팅 목록 프로필 이미지 URL, 권장 64x64pxhttps://…/profile.png
custom선택자유 형식 데이터, 최대 1KB{"grade":"vip"}
canBid선택true / false. 경매 입찰 자격 (부록 A · 옥션)true

서명하기

서명 예시입니다. 두 코드는 같은 토큰을 만듭니다 — Secret Key 가 base64 문자열이라 라이브러리에 따라 그대로 넣을지 디코딩해서 넣을지가 다르다는 점만 주의하세요.

// Java — jjwt. signWith 이 base64 문자열을 알아서 디코딩합니다
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

String base64EncodedSecretKey = "YOUR_BASE64_SECRET_KEY";   // Shoplive 담당자가 전달
long expiration = System.currentTimeMillis() + 12 * 60 * 60 * 1000L;   // 12시간

String jwt = Jwts.builder()
    .signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey)
    .setExpiration(new Date(expiration))
    .setIssuedAt(new Date())
    .claim("userId", "MEMBER_ID")     // 필수
    .claim("name", "NICKNAME")        // 선택 — 채팅 표시 이름
    .claim("userScore", 10)           // 선택
    .claim("userType", "VIP")         // 선택
    .compact();
// Node.js — jsonwebtoken. secretKey 를 직접 디코딩해서 넘겨야 합니다
const jwt = require('jsonwebtoken');

const base64EncodedSecretKey = 'YOUR_BASE64_SECRET_KEY';   // Shoplive 담당자가 전달
const secret = Buffer.from(base64EncodedSecretKey, 'base64');   // ← 디코딩 필요

const token = jwt.sign(
  {
    userId: 'MEMBER_ID',   // 필수
    name: 'NICKNAME',      // 선택 — 채팅 표시 이름
    userScore: 10,         // 선택
    userType: 'VIP'        // 선택
  },
  secret,
  { algorithm: 'HS256', expiresIn: '12h' }
);
Secret Key 는 백엔드에만 Secret Key 가 있으면 누구나 원하는 userId·userScore로 다른 사용자로 위장한 토큰을 만들 수 있습니다. 브라우저에서 번들되는 코드나 공개 저장소에 넣지 말고, 반드시 고객사 서버에서만 서명한 뒤 완성된 토큰만 프런트로 내려보내세요. 간편 인증(setUserObject())은 서명이 없어 권한 검증에 쓸 수 없습니다.

게스트도 구분해야 한다면 — 게스트 uid 받기 선택

사용자 정보를 넘기지 않은 게스트 시청자에게는 플레이어가 임의의 식별값 uid 를 발급합니다. 이 값을 고객사 쪽에서도 알아야 한다면 — 비로그인 시청자의 쿠폰 발급 이력을 묶거나, 자체 로그를 Shoplive 통계와 맞춰 볼 때 — messageCallback 에 ON_UID_ISSUED 를 등록합니다. 플레이어가 서버에 연결될 때마다 옵니다.

const messageCallback = {
  ON_UID_ISSUED: function (payload) {
    // payload.uid — 플레이어가 발급한 게스트 식별값 · payload.campaignKey — 어느 방송인지
    const stored = localStorage.getItem('shoplive_uid');
    if (stored !== payload.uid) {
      localStorage.setItem('shoplive_uid', payload.uid);
    }
  }
};
cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY', messageCallback });
게스트에게만 쓰고, 영구 ID 로 삼지 마세요
  • 로그인한 시청자는 userId 로 식별됩니다. 이 이벤트는 로그인 여부와 무관하게 오지만, 사용자 정보를 넘긴 시청자는 넘긴 userId 를 기준으로 하고 uid 는 게스트에게만 쓰세요.
  • 값이 바뀔 수 있습니다. 플레이어는 게스트 uid 를 브라우저에 2시간 동안 보관하고, 그 뒤에 다시 들어오면 새 값을 발급합니다. 브라우저 저장소를 지우거나 다른 브라우저로 들어와도 달라집니다. 받을 때마다 저장해 둔 값과 비교해 갱신하세요.

완료 확인

  • 로그인 후 채팅 전송 시 지정한 사용자 이름(회원 이름)이 표시된다
  • (입장 제한을 걸었다면) userScore/userType 기준에 맞는 사용자만 정상적으로 입장된다
  • 로그아웃 → cloud.shoplive.logout() 으로 사용자 정보를 초기화한 뒤 게스트로 시청된다

이전 ← 미션 1 · 방송 노출하기다음 → 미션 3 · 사용자 상호작용 처리하기

미션

미션 3 · 사용자 상호작용 처리하기

목표쿠폰 클릭·로그인 필요 상황을 처리하고, 상품 클릭 등 기본 동작이 있는 액션은 필요할 때만 원하는 대로 바꾼다
사전 준비미션 1(방법 A)의 initPlugin() 완료 · 쿠폰·상품 클릭을 다룬다면 Admin에서 해당 팝업·상품의 클릭 동작 설정 완료

쿠폰·상품·로그인처럼 자체 비즈니스 로직이 필요한 상호작용은 대부분 messageCallback을 통해 들어옵니다. 이 단계의 내용이 실제 연동 코드의 핵심입니다.

방법 B(링크로 열기)에서는 사용 불가 messageCallback은 페이지에 로드된 SDK의 initPlugin()에 등록하는 기능입니다. shoplive.show 링크(미션 1 방법 B)는 페이지에 SDK 자체가 로드되지 않으므로 콜백을 등록할 수 없습니다. 쿠폰·상품 클릭에 반응하는 로직이 필요하면 방법 A를 사용하세요.
관련 레퍼런스 Goods
  1. initPlugin() 호출 시 messageCallback에 액션 이름별로 함수를 등록합니다.
  2. 등록해두면 해당 액션이 발생할 때마다 자동으로 호출됩니다.
  3. (선택) 콜백 안에서 getPlayer()로 플레이어 UI를 갱신합니다.
const messageCallback = {
  DOWNLOAD_COUPON: function (payload) {
    console.log('쿠폰 클릭. coupon code: ' + payload.coupon);
    fetch('YOUR_COUPON_API')
      .then(function (res) { return res.json(); })
      .then(function (res) {
        if (res.success) {
          alert('쿠폰을 다운로드 받았습니다.');
          // 응답을 기다리는 사이 플레이어가 닫혔을 수 있어 여기서 집습니다 (미션 5)
          const player = cloud.shoplive.getPlayer(payload.campaignKey);
          if (player) player.sendMessage('removeCoupon');
        } else {
          alert('쿠폰이 모두 소진되었습니다.');
        }
      });
  },
  // CLICK_PRODUCT는 선택 — 정의 안 하면 기본 동작(새창 이동)을 그대로 사용합니다.
  // 현재 창 이동 등 다르게 동작시키고 싶을 때만 아래처럼 추가하세요.
  // CLICK_PRODUCT: function (payload) {
  //   location.href = payload.url;
  // },
  LOGIN_REQUIRED: function (payload) {
    // 게스트가 로그인 필요한 기능에 접근했을 때
  },
};
cloud.shoplive.initPlugin({
  accessKey: "YOUR_ACCESS_KEY",
  messageCallback
});
API 호출은 Admin이 아니라 고객사 코드에서 Admin에 "고객사 쿠폰 API URL"을 등록해두면 Shoplive가 대신 호출해주는 기능은 없습니다. 위 코드의 fetch('YOUR_COUPON_API')처럼, 쿠폰 클릭 시 고객사 서버 API를 호출하는 로직은 messageCallback 안에 직접 작성하는 것입니다.

핵심 액션

발생 시점의 굵은 번호는 화면 용어 가이드 그림의 번호입니다 — 시청자가 화면의 무엇을 눌렀을 때 그 액션이 오는지 바로 확인할 수 있습니다.

액션발생 시점payload용도
DOWNLOAD_COUPON클릭 동작이 "쿠폰 다운로드"로 설정된 팝업 클릭 ⑨ ⑩ ⑪campaignKey, coupon, popupResourceId, type(BANNER/COUPON/NOTICE)정의하지 않으면 아무 동작도 없음 — 쿠폰 발급을 자체 시스템과 연동할 때 필수
CUSTOM_ACTION클릭 동작이 "사용자 정의"로 설정된 팝업 클릭 ⑨ ⑩ ⑪campaignKey, id, type(BANNER/COUPON/NOTICE), payload.payload(Admin 쿠폰 편집 화면에서 설정한 사용자 정의 JSON)표준 필드로 부족할 때 원하는 데이터를 자유 형식으로 실어 보낼 수 있음
CLICK_PRODUCT상품 클릭 ⑥Goods정의 안 하면 자동으로 새창 이동. 현재 창 이동이나 URL 파라미터 추가 시 재정의
LOGIN_REQUIRED게스트가 로그인 필요한 기능 접근 ⑦campaignKey로그인 페이지로 이동시키는 로직 구현. 인증 방식 자체는 미션 2 참고
CLICK_SHARE_BTN공유 버튼 클릭 ⑫campaignKey, shareUrl, title등록하지 않으면 기본 동작(OS 공유 시트 또는 클립보드 복사)이 자동 실행됨. 등록하면 기본 동작이 대체되어 더 이상 실행되지 않으므로, 로깅만 하려는 경우에도 공유 자체를 아래처럼 직접 구현해야 함
LINK클릭 동작이 "링크"로 설정된 팝업 클릭 (또는 상품목록 상단 링크 클릭) ⑨ ⑩ ⑪campaignKey, popupResourceId, type(BANNER/COUPON/NOTICE), url재정의 안 하면 현재 창 이동이 기본 동작. 단 웹 PIP 가 켜져 있으면 플레이어를 띄운 채 이동합니다
CLICK_BACK_BUTTON플레이어의 뒤로가기 버튼 클릭 (ui.backButton을 켠 경우) ⑫없음재정의 안 하면 history.back(). 자체 라우터로 보내려면 재정의
ON_CLICK_CAMPAIGN_LIST_ITEM방송 목록(방법 C)에서 방송 하나를 클릭 — 플레이어 바깥입니다ak, campaign(Campaign)재정의 안 하면 모달 플레이어가 열림. 자체 상세 페이지로 보낼 때 재정의
ON_CLICK_CLOSE_PLAYER_MODAL모달 플레이어의 닫기 버튼 클릭 — 플레이어 바깥입니다campaign(Campaign)재정의 안 하면 모달이 닫힘. 닫기 전에 확인 창을 띄우는 등 가로챌 때 재정의
LINK_NEW_WINDOW클릭 동작이 "링크(새창)"로 설정된 팝업 클릭 (또는 상품목록 상단 링크 클릭) ⑨ ⑩ ⑪campaignKey, popupResourceId, type(BANNER/COUPON/NOTICE), url재정의 안 하면 새창 이동이 기본 동작
ON_CLICK_SCHEME_BUTTON플레이어 안의 스킴 버튼 클릭 — 상품 보기·담기·빼기, 옥션 낙찰자 결제 ⑥scheme(열 주소), schemeType, campaignKey재정의 안 하면 scheme 을 새 창으로 엽니다. schemeType 으로 무엇을 눌렀는지 구분해 고객사 로직으로 보낼 때 재정의 — 'GO_PRODUCT' · 'ADD_PRODUCT' · 'REMOVE_PRODUCT' · 'WINNER_CHECKOUT'(옥션 낙찰자 결제)
REQUEST_FULL_SCREEN
EXIT_FULL_SCREEN
플레이어에서 전체 화면 진입 · 해제를 요청할 때 ①campaignKey (해제는 없음)재정의 안 하면 플레이어 iframe 에 브라우저 전체 화면을 걸고 풉니다. 고객사가 자체 레이아웃으로 확대하려면 재정의
브라우저 PIP 로 들어온 페이지에서는 payload.url 에 파라미터가 하나 붙습니다 시청자가 브라우저 PIP 상태로 이동해 온 페이지에서는 CLICK_PRODUCT · LINK · LINK_NEW_WINDOW 의 payload.url 끝에 csl_bp 가 자동으로 붙습니다. 다음 페이지에서도 PIP 를 이어 가기 위한 표식입니다. 이 URL 을 고객사 로직에서 가공한다면 모르는 파라미터가 섞여 들어올 수 있다는 점을 감안하세요 — 지우면 이동한 페이지에서 PIP 가 끊깁니다.
CLICK_SHARE_BTN을 등록하면 기본 공유 동작이 사라집니다 공유 버튼을 누르면 navigator.share(지원 시 OS 공유 시트)나 클립보드 복사가 자동 실행됩니다. 공유되는 주소는 방송 키가 붙은 URL 입니다 — shareUrl(미션 1 initPlugin() 옵션)을 지정했으면 그 주소에 ?ck=, 지정하지 않았으면 현재 페이지 주소에 ?_sl_ck= 가 자동으로 추가됩니다. 붙는 파라미터와 그 주소로 들어왔을 때의 동작은 미션 4 · 공유 URL에 정리돼 있습니다. shareUrl에 이미 있는 쿼리 파라미터는 유지되므로 utm_source 등 마케팅 트래킹 파라미터를 미리 붙여둘 수 있습니다. 클립보드로 복사된 경우에는 플레이어에 "Copy to clipboard completed." 안내 창이 자동으로 뜹니다. CLICK_SHARE_BTN을 등록하는 순간 이 기본 동작은 더 이상 실행되지 않습니다. 클릭을 로깅만 하고 싶어도 아래처럼 공유 동작 자체를 직접 구현해야 합니다.
CLICK_SHARE_BTN: function (payload) {
  // 로깅 등 원하는 처리
  console.log('공유 클릭', payload.campaignKey);

  // 기본 동작을 그대로 유지하려면 직접 재구현
  // payload.shareUrl 이 비어 있으면 SDK 가 만들던 주소를 직접 만들어야 합니다
  if (navigator.share) {
    navigator.share({ title: payload.title || '', text: '', url: payload.shareUrl });
  } else if (navigator.clipboard) {
    navigator.clipboard.writeText(payload.shareUrl);
  }
}

배너(⑪)·쿠폰(⑨)·공지(⑩) 팝업은 Admin에서 팝업마다 클릭 시 동작을 설정합니다. 전체 동작은 아래와 같습니다.

Admin 설정개발 코드설명
쿠폰 다운로드필요 (DOWNLOAD_COUPON)콜백 미등록 시 아무 동작도 없음
링크 (현재 창)선택 (LINK)콜백 미등록 시 현재 창 이동이 기본 동작
링크 (새창)선택 (LINK_NEW_WINDOW)콜백 미등록 시 새 창 이동이 기본 동작
사용자 정의필요 (CUSTOM_ACTION)Admin에서 정의한 임의 payload 전달
더보기 팝업(이미지)불필요messageCallback 없이 플레이어가 자체적으로 이미지를 띄움
바텀시트 열기불필요messageCallback 없이 플레이어가 자체적으로 바텀시트를 띄움

클릭 동작을 설정했는데 등록한 콜백이 호출되지 않는다면, 위 표에서 "개발 코드 불필요" 항목으로 설정된 건 아닌지 Admin에서 확인해보세요.

알림만 받는 이벤트 — 기본 동작을 바꿀 것이 없는 것들

플레이어에서 올라오는 이벤트에는 어느 방송에서 일어난 일인지 알 수 있도록 payload.campaignKey 가 항상 함께 실립니다. 아래 표에 따로 적지 않은 이벤트도 마찬가지입니다.

아래 이벤트에는 SDK 기본 동작이 없습니다. 등록해도 무언가를 대체하지 않고, 그저 "이런 일이 일어났다"고 알려 줍니다. 애널리틱스 전송이나 고객사 화면 상태를 맞추는 데 씁니다.

ON_PLUGIN_INITIALIZED 의 liveMiniPreview 는 Admin 에 설정된 값에 SDK 기본값(위치 BOTTOM_RIGHT · 여백 16 · 노출 시간 300)을 채워 넣은 최종 결과입니다. 미니 프리뷰의 위치에 맞춰 고객사 UI 를 비켜 놓아야 할 때 이 값을 읽으면 됩니다.

이벤트발생 시점payload
ON_PLUGIN_INITIALIZEDinitPlugin() 이 완전히 끝났을 때. 초기화 전에 미리 부른 함수들이 모두 실행된 뒤에 옵니다liveMiniPreview — 미니 프리뷰의 실제 적용된 설정(position · marginTop · marginRight · marginBottom · marginLeft · dismissPreviewDuration)
ON_PLAYER_STARTED시청자가 플레이어에 들어와 재생이 시작될 때accessKey · campaignKey · isPreview(미리보기일 때만)
NOW_ONAIR_CHECKED진행 중인 방송이 있는지 확인된 시점campaignKey · campaignStatus · title · startedAt · tags
ON_CAMPAIGN_STATUS_CHANGED플레이어가 재생 중인 방송의 상태가 바뀔 때 — 방송 시작·종료가 여기에 옵니다. 플레이어가 열릴 때 현재 상태로 한 번 오고, 그 뒤 바뀔 때마다 옵니다status('READY' · 'ONAIR' · 'CLOSED' 등) · campaignKey
ON_CHANGED_VIEWER_STAT시청자 수·좋아요 수가 갱신될 때 (방송 중 수시로)viewerCount · likeCount · campaignKey
ON_PLAYER_MODAL_SHOWN
ON_PLAYER_MODAL_HIDDEN
모달 플레이어가 화면에 뜨고 사라질 때campaign(Campaign)
ON_LIVE_MINI_PREVIEW_SHOWN
ON_LIVE_MINI_PREVIEW_HIDDEN
미니 프리뷰 배너가 뜨고 사라질 때없음
ON_CLICK_CLOSE_LIVE_MINI_PREVIEW시청자가 미니 프리뷰의 닫기 버튼을 누를 때campaign(Campaign)
ON_PRODUCT_LIST_SHOWN상품 목록 팝업이 열릴 때productList — Goods 배열
ON_CHANGED_CAMPAIGN_PRODUCT_LIST방송에 걸린 상품 목록이 바뀔 때 — 운영자가 방송 중 상품을 추가·제거하면 옵니다productList · campaignKey
ON_RECEIVED_CHAT새 채팅이 도착할 때. 한 번에 여러 개가 묶여 옵니다chatList(배열) · campaignKey
ON_ERASED_CHAT운영자가 채팅을 삭제했을 때. 자체 채팅 목록을 만들었다면 이때 지웁니다lid(지워진 채팅 id) · campaignKey
ON_CHAT_INPUT_FOCUS_IN
ON_CHAT_INPUT_FOCUS_OUT
시청자가 채팅 입력창에 커서를 넣고 뺄 때. 모바일에서 키보드가 올라오는 시점입니다 — 아래 CHAT_READY 참고campaignKey
ON_FILTERED_CHATchatFilterPatterns에 걸려 채팅이 전송되지 않았을 때message · regExp(걸린 패턴)
ON_UID_ISSUED플레이어가 서버에 연결돼 시청자의 식별값이 정해질 때. 게스트 식별에 쓰는 법은 미션 2 · 게스트 uid 받기uid · campaignKey
const messageCallback = {
  ON_PLAYER_STARTED: function (payload) {
    analytics.track('live_player_started', { campaignKey: payload.campaignKey });
  },
  ON_PLAYER_MODAL_HIDDEN: function (payload) {
    // 모달이 닫혔으니 고객사 페이지 상태를 되돌린다
    document.body.classList.remove('live-open');
  }
};
cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY', messageCallback });
채팅 입력 중에는 컨테이너에 CHAT_READY 클래스가 붙습니다 시청자가 채팅 입력창에 커서를 넣으면 SDK 가 고객사가 만든 플레이어 div 에 CHAT_READY 클래스를 붙이고, 커서가 빠지면 뗍니다. 모바일에서 키보드가 플레이어를 가릴 때 콜백을 등록하지 않고 CSS 만으로 레이아웃을 바꿀 수 있습니다.
/* 채팅 입력 중에는 아래 영역을 접어 둔다 */
#shoplive-player.CHAT_READY ~ .my-product-section {
  display: none;
}

Goods payload 구조 (CLICK_PRODUCT)

필드설명
sku상품 코드
name상품명
brand브랜드
url상품 상세 페이지 URL — 정의 안 하면 이 값으로 새창 이동
goodsId상품 고유번호
campaignId방송 고유번호
description상품 설명
showingNowNOW 마크 표시 여부
salesStatusON_SALE / SOLDOUT / ALMOST_SOLDOUT
originalPrice판매가
discountedPrice할인가
discountPercentage할인율
currency통화 (예: KRW)
isCartOn카트 담기 여부

장바구니 담기(CLICK_PRODUCT_CART) 액션도 같은 Goods 구조를 payload로 사용합니다.

장바구니 담기 버튼 (별도 활성화 필요) 상품 리스트에 장바구니 버튼을 노출하려면 먼저 Shoplive(ask@shoplive.cloud)에 활성화를 요청해야 합니다. 활성화 후에는 CLICK_PRODUCT_CART 액션(payload: Goods)으로 장바구니 버튼 클릭을 처리할 수 있습니다.

Campaign payload 구조 (방송 정보)

방송 목록 클릭·모달 열림·미니 프리뷰 등 방송 단위로 발생하는 이벤트는 방송 정보를 함께 넘겨줍니다. 아래 여섯 가지는 항상 들어 있습니다.

필드설명
campaignKey방송 고유 키 — setPlayer·showFeaturedPlayerModal에 그대로 넘겨 쓸 수 있습니다
campaignStatus'READY'(예정) · 'ONAIR'(진행 중) · 'CLOSED'(종료)
title방송 제목
scheduledAt방송 예정 시각 (밀리초 timestamp)
startedAt실제 시작 시각. 아직 시작 전이면 null
endedAt실제 종료 시각. 아직 종료 전이면 null

이벤트에 따라 tags·memo·backgroundUrl 같은 값이 더 실려 오기도 합니다. 위 여섯 가지 외의 필드는 이벤트마다 다르니, 실제로 쓰기 전에 console.log(payload)로 확인하세요.

사용자 행동 로깅 (선택)

좋아요·공유·채팅 전송 등 플레이어 내 UI 클릭을 자체적으로 추적하고 싶다면 EVENT_LOG 액션을 등록합니다. 위에서 다루는 액션들과 달리 처리해야 할 비즈니스 로직이 없는 순수 로깅 용도라, 별도 활성화 요청 없이 바로 사용할 수 있습니다.

EVENT_LOG: function (payload) {
  console.log(payload.name, payload.feature, payload.parameter);
  // 예: payload.name이 'like_button', 'share_button', 'chat_send_button', 'coupon' 등
}
필드설명
name이벤트 이름 (버튼·액션별로 다름)
feature"click" / "action" / "show"
parameter이벤트별 참조 정보 (있는 경우)
campaignKey방송 고유번호

추적 가능한 이벤트 이름

payload.feature로 성격이 갈립니다 — click은 시청자가 직접 누른 것, show는 화면에 나타난 것, action은 그 밖의 동작입니다.

feature이벤트 이름
clickback_button · option_button · share_button · pip_button · like_button · chat_button · chat_send_button · mute_button · unmute_button · hide_controls · show_controls · video_expand_button · video_contract_button · landscape_button · portrait_button · coupon · notice · banner · product_list_button · product_list_item · product_list_banner · product_buy_button · product_add_to_watchlist · product_remove_from_watchlist · brand_favorite_button · benefit_button · benefit_contents · qna_list_button · qna_list_faq_tab · qna_list_all_answer_tab · qna_list_my_answer_tab · qna_chat_card · video_display_resolution_change · video_display_resolution_close · edit_nickname_close_button · edit_nickname_clear_button · edit_nickname_save_button · replay_play_button · replay_pause_button
showplayer_on_foreground · player_on_background · player_active_seconds · product_list_banner · show_qna_chat_card · quiz_question_show · quiz_answer_show
actionproduct_list_show · product_list_hide · product_list_expand · quiz_select · replay_seek · redirect_campaign

방송에 쿠폰·퀴즈·Q&A 등이 없으면 해당 이벤트는 발생하지 않습니다. 목록에 없는 이름이 들어온다면 새로 추가된 것이니 payload를 그대로 넘겨 기록해 두면 됩니다.

시청 시간 재기

플레이어가 활성 상태인 동안 player_active_seconds가 3초 간격으로 들어옵니다. 누적 시청 시간이 parameter.activatedDuration 에 이미 계산돼 오므로 직접 더할 필요가 없습니다.

parameter 필드설명
activatedDuration누적 시청 시간(밀리초). 탭이 가려진 구간은 빠져 있습니다
interval직전 보고 이후 경과한 시간(밀리초)
activatedAt시청을 시작한 시각 (밀리초 timestamp)
videoCurrentTime영상 재생 위치(초). 다시보기에서 어디를 보고 있는지
mode'LIVE' · 'REPLAY'(다시보기) · 'PREVIEW'(미리보기) — 라이브와 다시보기 시청 시간을 나눠 집계할 때 씁니다
EVENT_LOG: function (payload) {
  if (payload.name === 'player_active_seconds') {
    const p = payload.parameter;
    // 누적 시청 시간은 SDK 가 계산해 보내 줍니다 (밀리초)
    analytics.track('live_watch', {
      campaignKey: payload.campaignKey,
      mode: p.mode,                       // LIVE / REPLAY / PREVIEW
      seconds: Math.floor(p.activatedDuration / 1000)
    });
  }
}

누적은 방송이 진행 중(ONAIR)이고 탭이 화면에 보일 때 시작됩니다. 탭이 가려진 구간은 activatedDuration 에 포함되지 않으므로, player_on_foreground / player_on_background 로 따로 걸러 낼 필요가 없습니다.

완료 확인

  • 쿠폰 클릭 시 등록한 콜백이 호출된다
  • 상품 클릭 시 payload로 상품 정보가 넘어온다
  • 게스트가 채팅 등 로그인 필요 기능을 클릭하면 LOGIN_REQUIRED가 호출된다

이전 ← 미션 2 · 사용자 정보 연결다음 → 미션 4 · 다른 페이지에도 방송 알리기

미션

미션 4 · 다른 페이지에도 방송 알리기

목표방송을 띄운 페이지 밖에서도 시청자를 방송으로 데려온다
사전 준비미션 1 완료 (플레이어를 띄우는 페이지가 하나 있을 것)

미션 1 이 "방송을 어디에 그릴까"였다면, 이 미션은 그 페이지로 어떻게 오게 할까입니다. 세 가지 수단이 있고 서로 독립이니 필요한 것만 쓰세요.

미니 프리뷰 — 진행 중 방송을 다른 페이지 구석에 띄우기

진행 중인 방송이 있으면, 방송 페이지가 아닌 다른 페이지 구석에 작은 배너가 떠서 방송을 미리 보여 줍니다. 시청자가 누르면 모달 플레이어로 바로 이어집니다.

별도 호출은 필요 없습니다. 미리보기를 띄우고 싶은 페이지에 initPlugin()만 넣어 두면 진행 중 방송이 있을 때 자동으로 뜹니다. 그래서 보통은 공통 레이아웃에 초기화 코드를 한 번 넣는 것으로 끝납니다.

<!-- 공통 레이아웃(모든 페이지가 공유하는 head 영역)에 한 번만 -->
<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });
</script>

언제 뜨고, 언제 안 뜨나

원리는 하나입니다 — 시청자가 이미 방송을 보고 있는 페이지에서는 뜨지 않습니다. 방송으로 데려오는 것이 목적인데 이미 와 있으니 띄울 이유가 없고, 화면만 가립니다. 나머지는 중복 노출을 막기 위한 조건입니다.

이럴 때는 뜨지 않습니다왜
같은 페이지에 setPlayer 플레이어가 떠 있을 때이미 방송을 보고 있는 화면입니다. 플레이어가 닫히면 다시 뜹니다
모달 플레이어가 열려 있을 때
setOverall 의 대표 방송 영역이 살아 있을 때
페이지가 iframe 안에서 열렸을 때콘솔에 Disabled mini preview in iframe 이 남습니다
브라우저 PIP 로 넘어온 페이지일 때주소에 csl_bp 가 있으면 이미 PIP 로 보고 있는 중이라 건너뜁니다 (미션 3)
시청자가 닫은 뒤 유예 시간 안일 때바로 아래 닫으면 얼마나 안 뜨나 참고
닫으면 얼마나 안 뜨나 — 설정이 없으면 세션 내내 안 뜹니다 시청자가 배너를 닫으면 그 시각이 세션 저장소(sl_p.close_clicked_timestamp)에 기록되고, Admin 에 설정된 유예 시간(초) 동안 다시 뜨지 않습니다. 이 값이 설정돼 있지 않으면 유예가 아니라 차단입니다 — 그 세션 동안 영영 뜨지 않습니다. 유예 시간을 두고 싶다면 Shoplive 담당자에게 설정을 요청하세요.

같은 타임스탬프를 모달 플레이어를 닫을 때도 기록합니다. 그래서 시청자가 방송을 보다 닫으면 미니 프리뷰도 함께 잠잠해집니다.

개발 중 다시 보려면 브라우저 개발자 도구의 세션 저장소에서 위 키를 지우면 됩니다.

직접 띄우고 닫기 선택

자동으로 뜨는 것을 원하지 않고 고객사가 정한 시점에만 띄우고 싶다면, 초기화에서 자동 노출을 끄고 직접 호출합니다.

// 초기화 시 자동 노출을 끈다
cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  useAutoPreview: false
});

// 원하는 시점에 직접
cloud.shoplive.showLiveMiniPreview();     // 띄우기
cloud.shoplive.dismissLiveMiniPreview();  // 닫기

자동 노출을 껐더라도 위의 차단 조건은 그대로 적용됩니다 — showLiveMiniPreview() 를 직접 불러도 iframe 안이거나 유예 시간 중이면 아무 일도 일어나지 않습니다.

눌렀을 때 동작 바꾸기 선택

기본 동작은 모달 플레이어 열기입니다. 자체 라이브 페이지로 보내고 싶다면 아래처럼 가로챕니다.

cloud.shoplive.setOnClickLiveMiniPreview(function (campaign) {
  // campaign 에는 campaignKey · title · campaignStatus 등이 들어 있습니다
  location.href = '/live?ck=' + campaign.campaignKey;
});

배너가 뜨고 사라지고 닫히는 순간은 알림 이벤트(ON_LIVE_MINI_PREVIEW_SHOWN · ON_LIVE_MINI_PREVIEW_HIDDEN · ON_CLICK_CLOSE_LIVE_MINI_PREVIEW)로 알 수 있습니다.

위치와 크기

둘 다 고객사 코드로 바꾸지 않습니다 — 위치·여백은 Shoplive Admin 설정이고, 크기는 화면 폭이 정합니다. 주변 UI 를 비켜 놓을 때 참고하세요.

위치
무엇어떻게 정해지나
기본 위치Admin 의 position — 기본값은 우하단(BOTTOM_RIGHT)
화면 가장자리와의 여백Admin 의 marginTop · marginRight · marginBottom · marginLeft — 기본값은 각 16(px)
시청자가 옮기기드래그로 옮길 수 있습니다. 옮긴 자리는 세션 저장소(cslltposclnm)에 기억돼 페이지를 옮겨도 유지됩니다. Admin 설정으로 드래그를 막을 수도 있습니다

지금 어떤 값이 적용됐는지는 ON_PLUGIN_INITIALIZED 이벤트로 읽을 수 있습니다 — 미니 프리뷰가 뜰 자리를 피해 고객사 UI(플로팅 버튼 등)를 배치할 때 씁니다. 다만 시청자가 드래그로 옮긴 뒤에는 실제 위치가 달라집니다.

크기
방송 형태화면 폭너비높이
세로형 (9:15)1025px 이상17vw17vw × 15/9 (너비의 약 1.67배)
481 ~ 1024px26vw26vw × 15/9
480px 이하35vw35vw × 6/4 (너비의 1.5배)
가로형 (15:9)모든 폭26vw26vw × 9/15
방송 형태최소최대
세로형100 × 150px200 × 332px
가로형160 × 96px332 × 200px

비율이 9:16 · 16:9 가 아니라 9:15 · 15:9 입니다. 픽셀 단위로 자리를 맞출 때 주의하세요.

플레이어의 공유 버튼(ui.shareButton)이 만들어 내는 주소, 그리고 그 주소로 들어왔을 때의 동작입니다. shareUrl 을 지정했는지에 따라 붙는 파라미터와 이후 동작이 달라지므로 둘을 구분해서 보세요.

initPlugin 설정공유되는 주소그 주소로 들어오면
shareUrl 미지정 (기본)현재 페이지 주소 + ?_sl_ck={campaignKey}같은 페이지에 setOverall 이 있다면 SDK 가 이 값을 읽어 모달 플레이어를 자동으로 엽니다
shareUrl: 'https://my-shop.com/live'지정한 주소 + ?ck={campaignKey}자동으로 열리지 않습니다. 고객사 코드에서 ck 를 읽어 직접 플레이어를 띄워야 합니다
// shareUrl 을 지정했다면 — 받는 쪽에서 직접 열어 줍니다
const ck = new URLSearchParams(location.search).get('ck');
if (ck) {
  cloud.shoplive.showFeaturedPlayerModal({ campaignKey: ck });
  // 또는 cloud.shoplive.setPlayer('shoplive-player', { campaignKey: ck });
}
모달이 열리면 주소도 따라 바뀝니다 어느 경로로든 모달 플레이어가 열리면 SDK 가 현재 주소에 _sl_ck 를 붙이고, 닫으면 되돌립니다. 덕분에 재생 중 주소창을 복사해 보내면 받는 사람도 같은 방송이 열린 상태로 들어오고, 모바일에서 뒤로가기를 누르면 페이지를 떠나지 않고 모달만 닫힙니다. 자체 라우터와 충돌하면 disableHistoryHook 으로 끌 수 있습니다.

마케팅 유입 경로를 남기려면 공유·광고 주소에 utm_source · utm_medium · utm_campaign · utm_content 를 붙이면 됩니다. SDK 가 이 값을 읽어 시청·전환 기록에 함께 남깁니다 (미션 6).

utm_source 가 없으면 광고 ID 파라미터를 대신 씁니다 — adid · adId · adld · adLd · ad-ld · ad-id · ad-Id 중 먼저 발견되는 값입니다. 광고 플랫폼이 adid 로 내보내는 경우 주소를 고치지 않아도 그대로 잡힙니다.

방송 카드 · 진행 중 커버 선택

목록 전체(방법 C)까지는 필요 없고 특정 방송 하나만, 또는 진행 중일 때만 무언가를 보여 주고 싶을 때 씁니다.

함수무엇이 그려지나
setLivePreview(containerId, { campaignKey, onClick })지정한 방송의 미리보기 카드. 진행 중이면 영상이, 종료됐으면 포스터 이미지가 나옵니다. onClick 을 주면 눌렀을 때 그 함수가 실행됩니다
destroyLivePreview(containerId?)위 카드를 제거합니다. 인자를 생략하면 페이지의 모든 카드
setOnAirCover(containerId)진행 중인 방송이 있을 때만 해당 영역에 커버가 나타납니다. 없으면 아무것도 그리지 않습니다
setLandscapeOnAirCover(containerId)위와 같되 가로형 방송용 커버
<div id="live-card"></div>
<script>
  cloud.shoplive.setLivePreview('live-card', {
    campaignKey: 'YOUR_CAMPAIGN_KEY',
    onClick: function (campaign) {
      cloud.shoplive.showFeaturedPlayerModal({ campaignKey: campaign.campaignKey });
    }
  });
</script>

<div id="on-air-cover"></div>
<script>
  cloud.shoplive.setOnAirCover('on-air-cover');
</script>

커버가 실제로 떴는지는 ON_ONAIR_COVER_SHOWN 이벤트의 payload.show 로 알 수 있습니다 — 방송이 없어 커버가 안 뜰 때 그 자리를 다른 배너로 채우는 식으로 쓸 수 있습니다.

완료 확인

  • 방송이 진행 중일 때, 초기화 코드를 넣은 다른 페이지 구석에 미니 프리뷰가 뜬다
  • 미니 프리뷰를 누르면 모달 플레이어가 열린다 (또는 고객사가 지정한 동작이 실행된다)
  • 재생 중 주소창을 복사해 새 탭에 붙여넣으면 같은 방송이 열린 상태로 뜬다

이전 ← 미션 3 · 사용자 상호작용 처리하기다음 → 미션 5 · 플레이어 제어하기

미션

미션 5 · 플레이어 제어하기 선택

목표이미 떠 있는 플레이어를 고객사 코드에서 조작한다 — 쿠폰 지우기, 음소거, 안내창 띄우기 등
사전 준비미션 1 로 플레이어가 떠 있는 상태

미션 3 이 "플레이어가 알려 오는 것을 받는" 쪽이라면, 이 미션은 반대 방향입니다. 플레이어 객체를 집어서 명령을 보냅니다.

SDK 로드부터 명령을 보내기까지 한 페이지에 모으면 아래와 같습니다. 1 · 2 · 4 는 미션 1 방법 A 그대로이고, 이 미션이 더하는 것은 3 과 5 입니다.

<!-- 1. SDK 로드 -->
<script src="https://static.shoplive.cloud/shoplive.js"></script>

<!-- 2. 플레이어가 들어갈 자리 -->
<div id="shoplive-player"></div>

<!-- 3. (선택) 플레이어를 조작할 고객사 UI — 여기서는 플레이어 밖의 음소거 버튼 -->
<button type="button" id="mute-btn">음소거</button>

<script>
  // 4. 초기화하고 플레이어를 띄웁니다 (미션 1)
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });
  cloud.shoplive.setPlayer('shoplive-player', { campaignKey: 'YOUR_CAMPAIGN_KEY' });

  // 5. (선택) 명령을 보냅니다 — 여기서는 밖의 버튼을 눌렀을 때
  document.getElementById('mute-btn').addEventListener('click', function () {
    const player = cloud.shoplive.getPlayer('YOUR_CAMPAIGN_KEY');
    if (!player) return;              // 아직 안 떴다면 아무것도 하지 않습니다
    player.sendMessage('mute');
  });
</script>

3 · 5 는 이 예제에서 고른 하나의 방식일 뿐입니다. 플레이어 밖에 버튼을 두고 누를 때 음소거하는 모습을 보였지만, 명령을 보내는 시점은 무엇이든 됩니다 — 고객사 페이지의 다른 UI, 스크롤·타이머 같은 자체 로직, 또는 미션 3 의 콜백 안 어디서든 getPlayer() 로 집어 보내면 됩니다. 플레이어를 그냥 띄우기만 할 것이라면 이 미션 자체가 필요 없습니다.

setPlayer() 바로 다음 줄에서 getPlayer() 를 부르면 undefined 입니다 initPlugin() 은 서버에서 설정을 받아오는 비동기 동작이고, 그동안 setPlayer() 는 대기열에 들어갔다가 나중에 실행됩니다. 그래서 바로 다음 줄에는 아직 플레이어가 존재하지 않습니다.

위 예제처럼 시청자가 버튼을 누르는 시점에 집는 것이 가장 간단합니다. 페이지가 열리자마자 무언가 보내야 한다면 재생이 시작됐다는 알림을 받은 뒤에 집으세요.
cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  messageCallback: {
    // 재생이 시작된 직후 — 이 시점에는 플레이어가 반드시 있습니다
    ON_PLAYER_STARTED: function (payload) {
      const player = cloud.shoplive.getPlayer(payload.campaignKey);
      if (player) player.sendMessage('hideControls');
    }
  }
});

cloud.shoplive.setPlayer('shoplive-player', { campaignKey: 'YOUR_CAMPAIGN_KEY' });

플레이어 집어내기

호출무엇을 돌려주나
getPlayer()지금 활성인 플레이어 하나. 진행 중(ONAIR) 방송이 있으면 그것을 우선하고, 없으면 마지막으로 만들어진 플레이어를 돌려줍니다
getPlayer('CAMPAIGN_KEY')그 방송의 플레이어. playerAlias 를 지정했다면 그 별명으로도 찾습니다
getActivePlayers()지금 살아 있는 플레이어 배열. 한 페이지에 여러 개를 띄웠을 때 전부 순회할 때 씁니다
플레이어가 없으면 undefined 입니다 아직 setPlayer 가 실행되기 전이거나 모두 정리된 뒤라면 getPlayer() 는 아무것도 돌려주지 않습니다. messageCallback 이 호출되는 순간은 플레이어가 떠 있는 시점이라 안전하지만, 그 안에서 fetch 등을 기다린 뒤라면 다릅니다 — 기다리는 사이 시청자가 모달을 닫았을 수 있습니다. 비동기 대기 뒤에는 플레이어를 다시 집고 if (player) 로 확인하세요.

플레이어 객체에서 바로 읽을 수 있는 값은 아래와 같습니다.

속성설명
campaignKey이 플레이어가 재생 중인 방송 키
campaignStatus'READY' · 'ONAIR' · 'CLOSED' — 실시간으로 갱신됩니다
playerAlias지정했다면 그 별명
container플레이어가 들어 있는, 고객사가 만든 div 엘리먼트

보낼 수 있는 명령

player.sendMessage(명령, payload) 형태입니다. 아래는 고객사 연동에서 쓰는 것들입니다.

명령payload동작
removeCoupon없음화면에 떠 있는 쿠폰 팝업을 지웁니다. 쿠폰 발급 처리를 끝낸 뒤 부르는 것이 일반적입니다
removeBanner · removeNotice없음배너 · 공지 팝업을 지웁니다
mute · unmute없음음소거 켜기 · 끄기
playVideo · pauseVideo없음재생 · 일시정지
videoRefresh없음영상을 다시 불러옵니다. 네트워크가 끊겼다 돌아온 뒤 복구용
hideControls · showControls없음컨트롤 UI 숨기기 · 보이기
dismissGoodsList없음열려 있는 상품 목록 팝업을 닫습니다
GET_CURRENT_PLAYBACK_TIME없음현재 재생 위치를 물어봅니다. 답은 곧바로 오지 않고 CURRENT_PLAYBACK_TIME 이벤트로 돌아옵니다 — 아래 참고
showDialog{ title, message }
선택: cancelText · confirmText · confirmTextColor · confirmBackgroundColor
플레이어 위에 안내 창을 띄웁니다. confirmText 를 주면 확인 · 취소 두 버튼이 되고, 주지 않으면 닫기 버튼 하나만 나옵니다. title 은 비우면 생략됩니다
showToast{ message }
선택: duration · position
짧은 토스트 메시지를 띄웁니다. duration 은 밀리초이며 0 이면 직접 지울 때까지 남습니다. position 은 기본 'BOTTOM'
답이 돌아오는 명령 — 재생 위치 묻기 대부분의 명령은 보내면 끝이지만, GET_CURRENT_PLAYBACK_TIME 은 물어보고 답을 받는 유일한 명령입니다. 답은 messageCallback 의 CURRENT_PLAYBACK_TIME 으로 옵니다.
// 1) 물어본다
cloud.shoplive.getPlayer(PAGE_CAMPAIGN_KEY).sendMessage('GET_CURRENT_PLAYBACK_TIME');

// 2) 답을 받는다
const messageCallback = {
  CURRENT_PLAYBACK_TIME: function (payload) {
    payload.playbackTimeInSeconds;  // 132.4  (숫자, 초)
    payload.playbackTime;           // '00:02:12'  (HH:mm:ss 문자열)
  },
};

라이브면 방송 시작 시각부터 흐른 시간, 다시보기면 영상의 현재 재생 위치입니다. 방송 시작 시각을 아직 모르면 0 이 옵니다.

const messageCallback = {
  DOWNLOAD_COUPON: function (payload) {
    fetch('YOUR_COUPON_API', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ coupon: payload.coupon })
    })
      .then(function (res) { return res.json(); })
      .then(function (res) {
        // 응답을 기다리는 사이 플레이어가 닫혔을 수 있으므로 여기서 다시 집습니다
        const player = cloud.shoplive.getPlayer(payload.campaignKey);
        if (!player) return;

        if (res.success) {
          player.sendMessage('showDialog', {
            title: '쿠폰 발급 완료',
            message: '쿠폰 번호 ' + payload.coupon + ' 이(가) 발급되었습니다.'
          });
          player.sendMessage('removeCoupon');   // 받은 쿠폰은 화면에서 치운다
        } else {
          player.sendMessage('showToast', { message: '쿠폰이 모두 소진되었습니다.' });
        }
      })
      .catch(function () {
        const player = cloud.shoplive.getPlayer(payload.campaignKey);
        // 실패했을 때 아무것도 하지 않으면 시청자는 눌러도 반응이 없다고 느낍니다
        if (player) player.sendMessage('showToast', { message: '잠시 후 다시 시도해 주세요.' });
      });
  }
};

cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY', messageCallback });

위 예제에서 짚어 둘 것이 두 가지 있습니다.

  • getPlayer() 가 아니라 getPlayer(payload.campaignKey) 입니다. 인자 없이 부르면 진행 중(ONAIR) 방송이 우선되므로, 한 페이지에 플레이어가 여럿일 때 쿠폰을 누른 플레이어가 아닌 쪽에 안내창이 뜰 수 있습니다. 이벤트 payload 에 campaignKey 가 함께 오니 그대로 넘기면 됩니다.
  • 플레이어는 await/then 뒤에 다시 집습니다. 서버 응답을 기다리는 동안 시청자가 모달을 닫으면 그 플레이어는 사라집니다. 콜백 첫 줄에서 잡아 둔 객체로 메시지를 보내면 아무 데도 도착하지 않습니다.

위 표에 없는 명령 이름을 코드에서 보셨다면 모바일 SDK 웹뷰나 Shoplive 운영 도구가 쓰는 내부 명령입니다. 웹 연동에서는 쓰지 않습니다.

완료 확인

  • getPlayer() 가 플레이어 객체를 돌려주고 campaignKey 가 고객사가 띄운 방송과 일치한다
  • 쿠폰 클릭 후 removeCoupon 을 보내면 쿠폰 팝업이 사라진다
  • showDialog 로 보낸 안내 창이 플레이어 위에 뜬다

이전 ← 미션 4 · 다른 페이지에도 방송 알리기다음 → 미션 6 · 측정 — 유입과 구매

미션

미션 6 · 측정 — 유입과 구매

목표어느 채널로 들어와 방송을 봤고 실제로 구매까지 갔는지를 Shoplive 통계에 연결한다
사전 준비주문 완료 페이지에 스크립트를 넣을 수 있어야 함 · (로그인 서비스라면) 시청 시와 같은 userId

시청자가 상품을 눌러 상품 페이지로 이동하는 순간 SDK 의 추적은 끝납니다. 그 뒤 결제가 성사됐는지는 고객사 페이지에서만 알 수 있으므로, 구매 완료 시점에 한 번 알려 주어야 "이 방송이 매출을 얼마나 만들었는가"가 집계됩니다.

어떤 구매가 전환으로 잡히나 시청자가 라이브(또는 다시보기) 영상을 3초 이상 본 뒤, 그 영상에 등록된 상품을 28일 이내에 구매하면 전환으로 기록됩니다. 기준은 마지막으로 본 영상이며, 라이브(다시보기)와 숏폼은 각각 따로 집계됩니다.

어디서 들어왔는지 — 마케팅 유입 추적

방송 페이지 주소에 utm_ 으로 시작하는 파라미터를 붙여 두면 SDK 가 그 값을 읽어 어느 채널로 들어온 시청자인지 함께 기록합니다. 인스타그램 배너로 들어온 사람과 문자 메시지로 들어온 사람의 시청·구매를 나눠 볼 수 있습니다.

파라미터무엇을 담나예시
utm_source유입 채널instagram
utm_medium매체 타입banner
utm_campaign마케팅 캠페인spring_sale
utm_content마케팅 소재type2

넷을 다 넣을 필요는 없고 필요한 것만 붙이면 됩니다.

https://my-shop.com/live?utm_source=instagram&utm_medium=banner&utm_campaign=spring_sale
SDK 는 initPlugin() 을 부르는 순간의 페이지 주소에서 읽습니다 그래서 그 시점에 주소창에 쿼리가 남아 있어야 합니다. SPA 에서 라우팅하며 쿼리스트링을 털어낸 뒤 초기화하거나, 광고 링크를 받아 쿼리 없는 주소로 리다이렉트한 뒤 플레이어를 띄우면 유입 정보가 통째로 사라집니다 — 오류도 나지 않고 그냥 빈 값으로 기록됩니다.

여기서 읽은 값은 구매 전환 보고에도 자동으로 따라붙습니다. 따로 넘길 것은 없고 유입 → 시청 → 구매가 한 줄로 이어집니다.

집계 결과는 Shoplive Admin 의 통계에서 유입 채널별로 나눠 볼 수 있습니다.

광고 플랫폼에서 들어올 때

광고 플랫폼은 링크에 자체 추적 파라미터를 붙여 보냅니다. SDK 가 페이지 주소를 통째로 플레이어까지 넘기므로 고객사가 따로 처리할 것은 없습니다 — 다만 그 주소가 온전히 남아 있어야 합니다.

  • 웹 — 광고 링크가 도착한 랜딩 페이지에서 쿼리스트링을 지우지 않습니다. 리다이렉트를 한 번 거친다면 쿼리를 그대로 이어 붙여 넘기세요
  • 하이브리드 앱(웹뷰) — 앱이 광고 링크를 받아 웹뷰를 열 때 쿼리를 웹뷰 URL 로 그대로 복사해야 합니다. 웹뷰 자체 설정은 부록 D를 보세요
  • 네이티브 앱 — 웹 SDK 가 아니라 iOS·Android 네이티브 SDK 의 딥링크 처리 영역입니다. Shoplive 담당자에게 문의하세요

광고 식별자는 adId 로도 받습니다 — adid · ad-id 처럼 표기가 달라도 인식합니다.

구매 전환 1단계 · 어디에 넣나

주문이 확정된 뒤 한 번만 열리는 페이지 — 보통 주문 완료·결제 완료 페이지입니다. 장바구니나 결제 진행 화면에 넣으면 이탈한 주문까지 구매로 집계되고, 새로고침으로 여러 번 열리는 페이지에 넣으면 같은 주문이 중복 보고됩니다.

구매 전환 2단계 · 보내기

SDK 로드 → 초기화 → 사용자 정보 → 보고 순서입니다. 초기화가 끝나기 전에 sendConversionEvent() 를 불러도 SDK 가 대기시켰다가 실행하므로 순서 때문에 실패하지는 않습니다.

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });

  // 로그인 사용자라면 시청할 때와 "같은" userId 를 넣습니다 (3단계 참고)
  cloud.shoplive.setUserObject({ userId: 'MEMBER_ID' });

  cloud.shoplive.sendConversionEvent({
    accessKey: 'YOUR_ACCESS_KEY',   // 필수
    eventType: 'purchase',          // 필수 — 'purchase'(구매 완료) / 'cartIn'(장바구니 담기)
    orderId: 'UNIQUE_ORDER_ID',     // 고객사 주문번호
    products: [
      { productId: 'PRODUCT_ID_1', sku: 'PRODUCT_SKU_1', purchaseUnitPrice: 10000, purchaseQuantity: 1, currency: 'KRW' },
      { productId: 'PRODUCT_ID_2', sku: 'PRODUCT_SKU_2', purchaseUnitPrice: 15000, purchaseQuantity: 2, currency: 'KRW' }
    ]
  });
</script>
필드필수설명
accessKey필수초기화에 넘겼더라도 이 객체에 다시 넣어야 합니다 — 없으면 콘솔에 Invalid request만 찍히고 전송되지 않습니다
eventType필수어떤 전환인지 — 구매 완료는 'purchase', 장바구니 담기는 'cartIn'
orderId선택고객사 주문번호. 그대로 함께 전송됩니다 — 어느 주문의 전환인지 나중에 맞춰 보려면 넣어 두세요
products선택구매한 상품 배열 — 각 항목의 필드는 아래 표 참고
timestamp선택구매 시각. 넣지 않으면 호출 시각이 쓰입니다
custom선택함께 남기고 싶은 임의 값
userObject · userJWT선택이 호출에만 쓸 사용자 정보. 넣지 않으면 setUserObject() · setUserJWT() 로 넘긴 값이 쓰입니다

주소에 utm_source 등 UTM 파라미터가 있으면 이 요청에 함께 실려 나갑니다 — 광고·공유 유입별 전환을 나눠 보려면 유입 주소에 UTM 을 붙여 두세요.

products 배열의 각 항목입니다.

필드필수설명
productId필수Shoplive 에 등록된 상품 ID
sku필수상품 코드
purchaseUnitPrice필수구매 단가
purchaseQuantity필수구매 수량
currency필수통화 (예: 'KRW')
customerProductId선택고객사 자체 상품 ID

필수 표기는 SDK 타입 선언 기준입니다. 실제로는 accessKey 와 eventType 만 검사하고 보내므로 일부가 빠져도 요청은 그대로 전송됩니다 — 대신 집계에서 조용히 누락될 수 있으니 채워서 보내세요.

구매 전환 3단계 · 시청 기록과 이어 붙는 기준

시청 기록과 구매 보고는 서로 다른 요청으로 따로 나갑니다. 영상을 3초 이상 본 시점에 플레이어가 시청 기록을 보내고, 구매는 위의 sendConversionEvent() 가 보냅니다. 두 요청을 서버가 식별자로 맞춰 붙여서 전환이 완성됩니다. 그래서 식별자가 어긋나면 양쪽 기록이 남아 있어도 전환으로 집계되지 않습니다 — 아래 세 가지 중 하나가 맞아야 합니다.

구분이어 붙는 기준고객사가 맞춰야 할 것
로그인 사용자userId방송을 볼 때 미션 2로 넘긴 값과 완전히 같은 값을 구매 완료 페이지에서도 넘깁니다. 해싱했다면 같은 해시값이어야 합니다
비로그인(게스트)브라우저에 저장된 익명 식별자호출할 것은 없지만 같은 브라우저 · 같은 도메인이어야 합니다
자체 식별자가 있다면anonIdinitPlugin({ anonId }) 로 넘기면 로그인 없이도 시청·구매를 묶을 수 있습니다. 페이지 주소에 ?anonId= 로 넘겨도 인식됩니다
게스트 전환은 도메인이 갈리면 끊깁니다 익명 식별자는 브라우저 저장소에 보관되고, 저장소는 도메인별로 분리됩니다. 방송을 보여준 페이지와 주문 완료 페이지의 도메인이 다르면(예: www.shop.com 에서 보고 order.shop.com 에서 결제) 같은 사람으로 이어지지 않습니다. 도메인을 맞추기 어렵다면 userId 나 anonId 로 묶는 것이 확실합니다.

쇼핑몰 플랫폼에서 선택

주문 완료 페이지를 직접 만들 수 없는 플랫폼에서는 아래 자리에 같은 호출을 넣습니다. 주문번호와 상품 정보를 플랫폼이 주는 값으로 채우는 부분만 다릅니다.

Cafe24

스킨의 order_result.html(주문 완료 화면) </body> 직전에 넣습니다. 주문번호는 Cafe24 가 채워 주는 {$order_id} 로 받고, 주문 상품 목록은 Cafe24 API 로 조회합니다.

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });

  const orderId = '{$order_id}';   // Cafe24 가 실제 주문번호로 바꿔 줍니다

  // 회원 주문이라면 시청할 때와 같은 userId 를 넣습니다
  // cloud.shoplive.setUserObject({ userId: 'MEMBER_ID' });

  cloud.shoplive.sendConversionEvent({
    accessKey: 'YOUR_ACCESS_KEY',
    eventType: 'purchase',
    orderId: orderId,
    products: [
      // Cafe24 API 로 조회한 주문 상품을 { productId, sku, purchaseUnitPrice, purchaseQuantity, currency } 형태로 채웁니다
    ]
  });
</script>

주문 상품을 Cafe24 API(CAFE24API.getOrderDetailInfo)로 조회하려면 앱 키가 필요합니다. 이 값은 Shoplive 담당자에게 문의하세요.

Shopify

주문 완료 화면에 스크립트를 직접 넣을 수 없으므로, 사용자 지정 픽셀에 등록해 checkout_completed 시점에 호출합니다.

const script = document.createElement('script');
script.src = 'https://static.shoplive.cloud/shoplive.js';
script.async = true;
document.head.appendChild(script);

script.onload = function () {
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });

  analytics.subscribe('checkout_completed', function (event) {
    const checkout = event.data.checkout;

    // 회원 주문이면 시청할 때와 같은 기준으로 userId 를 넣습니다
    const customerId = checkout.order.customer && checkout.order.customer.id;
    if (customerId) {
      cloud.shoplive.setUserObject({ userId: String(customerId) });
    }

    cloud.shoplive.sendConversionEvent({
      accessKey: 'YOUR_ACCESS_KEY',
      eventType: 'purchase',
      orderId: checkout.order.id,
      products: checkout.lineItems.map(function (item) {
        return {
          productId: item.variant.product.id,
          sku: item.variant.sku,
          purchaseUnitPrice: parseFloat(item.variant.price.amount),
          purchaseQuantity: item.quantity,
          currency: item.variant.price.currencyCode
        };
      })
    });
  });
};

완료 확인

  • 주문 완료 페이지에서 네트워크 탭에 /conversion/event 요청이 한 번 기록된다
  • 새로고침·뒤로가기로 같은 주문이 여러 번 보고되지 않는다
  • (로그인) 요청이 나갈 때의 userId 가 방송을 볼 때 넘긴 값과 같다
  • 집계 결과는 Shoplive Admin 의 전환 리포트에서 확인합니다 — 반영에 시간이 걸릴 수 있습니다

이전 ← 미션 5 · 플레이어 제어하기다음 → 전체 예제

시작하기

전체 예제 — 미션을 한 페이지에 적용하기

지금까지의 내용을 한 페이지에 모으면 아래와 같은 형태가 됩니다. 실제 서비스에 맞게 YOUR_ACCESS_KEY, YOUR_CAMPAIGN_KEY와 콜백 내부 로직만 채우면 됩니다. 구매 전환(미션 6)은 이 페이지가 아니라 주문 완료 페이지에 들어가므로 아래에 따로 뒀습니다.

라이브 페이지

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  // ── 미션 3 · 사용자 상호작용 처리
  const messageCallback = {
    DOWNLOAD_COUPON: function (payload) {
      // 고객사 쿠폰 발급 API 를 부르고, 성공했을 때만 화면에서 쿠폰을 치운다 (미션 5)
      fetch('YOUR_COUPON_API', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ coupon: payload.coupon })
      })
        .then(function (res) { return res.json(); })
        .then(function (res) {
          // 응답을 기다리는 사이 플레이어가 닫혔을 수 있어 여기서 집는다
          const player = cloud.shoplive.getPlayer(payload.campaignKey);
          if (!player) return;
          if (res.success) {
            player.sendMessage('removeCoupon');
          } else {
            player.sendMessage('showToast', { message: '쿠폰이 모두 소진되었습니다.' });
          }
        });
    },
    LOGIN_REQUIRED: function () {
      location.href = '/login?returnTo=' + encodeURIComponent(location.href);
    },
    ON_CLICK_CAMPAIGN_LIST_ITEM: function (payload) {
      // 기본 동작은 모달 열기입니다. 자체 방송 상세 페이지로 보내고 싶을 때만 이렇게 재정의합니다
      // (모달로 열리는 기본 동작을 그대로 쓸 거라면 이 콜백을 등록하지 마세요)
      location.href = '/live/' + payload.campaign.campaignKey;
    }
  };

  // ── 미션 1 · 초기화 (미션 4 · 공유 URL 지정)
  cloud.shoplive.initPlugin({
    accessKey: 'YOUR_ACCESS_KEY',
    messageCallback: messageCallback,
    shareUrl: 'https://my-shop.com/live'
  });

  // ── 미션 2 · 로그인 사용자라면 연결 (게스트라면 이 블록 전체를 건너뜁니다)
  cloud.shoplive.setUserObject({ userId: 'YOUR_HASHED_USER_ID', userName: 'YOUR_USER_NAME' });

  // 이 페이지에 고정으로 띄울 방송
  const PAGE_CAMPAIGN_KEY = 'YOUR_CAMPAIGN_KEY';
</script>

<!-- ── 미션 1 · 방법 A — 특정 방송 하나를 이 자리에 -->
<div id="shoplive-player"></div>
<script>
  cloud.shoplive.setPlayer('shoplive-player', {
    campaignKey: PAGE_CAMPAIGN_KEY,
    ui: { shareButton: true }
  });
</script>

<!-- ── 미션 1 · 방법 C — 대표 방송 + 목록을 템플릿으로 (방법 A 와 택일) -->
<!--
<div id="shoplive-overall-area"></div>
<script>
  cloud.shoplive.setOverall('shoplive-overall-area');
</script>
-->

<!-- ── 미션 4 · 공유 링크로 들어온 경우 그 방송을 연다 -->
<script>
  const ck = new URLSearchParams(location.search).get('ck');
  // 이 페이지에 이미 떠 있는 방송이면 모달을 겹쳐 열지 않는다
  if (ck && ck !== PAGE_CAMPAIGN_KEY) {
    cloud.shoplive.showFeaturedPlayerModal({ campaignKey: ck });
  }
</script>

주문 완료 페이지

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });

  // 방송을 볼 때와 "같은" userId 여야 전환으로 이어집니다
  cloud.shoplive.setUserObject({ userId: 'YOUR_HASHED_USER_ID' });

  cloud.shoplive.sendConversionEvent({
    accessKey: 'YOUR_ACCESS_KEY',
    eventType: 'purchase',
    orderId: 'UNIQUE_ORDER_ID',
    products: [{
      productId: 'PRODUCT_ID',
      sku: 'PRODUCT_SKU',
      purchaseUnitPrice: 10000,
      purchaseQuantity: 1,
      currency: 'KRW'
    }]
  });
</script>

이전 ← 미션 6 · 측정 — 유입과 구매다음 → 부록

부록

부록 · 특정 상황에서만

미션에 들어가지 않는, 특정 상황에서만 필요한 기능입니다.

A. 활성화가 필요한 기능

아래 네 가지는 계정에 기능이 켜져 있어야 동작합니다. 코드를 넣어도 버튼 자체가 나타나지 않는다면 활성화 여부를 먼저 확인하세요 — 요청은 ask@shoplive.cloud 로 합니다.

브랜드 좋아요

플레이어 안의 브랜드 즐겨찾기 버튼입니다. 시청자가 누르면 ON_CLICK_BRAND_FAVORITE_BUTTON 이 오고, 고객사 시스템에 저장한 뒤 그 결과를 플레이어에 되돌려 줍니다.

const messageCallback = {
  // 플레이어가 시작되거나 브랜드 정보가 바뀔 때 — 현재 사용자의 좋아요 상태를 알려 준다
  ON_CHANGED_BRAND_FAVORITE: function (payload) {
    const player = cloud.shoplive.getPlayer(payload.campaignKey);
    if (!player) return;
    player.sendMessage('SEND_COMMAND_MESSAGE', {
      command: 'SET_BRAND_FAVORITE',
      payload: { identifier: payload.identifier, favorite: true }
    });
  },
  // 시청자가 버튼을 눌렀을 때 — 고객사 서버에 저장한다
  ON_CLICK_BRAND_FAVORITE_BUTTON: function (payload) {
    const { identifier, favorite } = payload;
    fetch('YOUR_FAVORITE_API', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ identifier, favorite })
    });
  }
};

cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY', messageCallback });

플레이어를 띄울 때 초기 상태를 함께 넘길 수도 있습니다 — setPlayer(id, { campaignKey, brand: { identifier, favorite } }).

장바구니 담기

상품 목록에 장바구니 버튼이 노출되고, 누르면 CLICK_PRODUCT_CART 가 Goods payload 와 함께 옵니다. 고객사 장바구니에 담은 뒤, 담긴 상태를 플레이어에 되돌려 주면 버튼 모양이 바뀝니다.

CLICK_PRODUCT_CART: function (payload) {
  addToMyCart(payload).then(function () {
    // 담는 사이 플레이어가 닫혔을 수 있어 여기서 집는다 (미션 5)
    const player = cloud.shoplive.getPlayer(payload.campaignKey);
    if (!player) return;
    player.sendMessage('SEND_COMMAND_MESSAGE', {
      command: 'SET_PRODUCT_CART_ON',
      payload: { products: [{ sku: payload.sku, isCartOn: true }] }
    });
  });
}

Shopify 스토어에서 platform: 'SHOPIFY' 를 쓰면 장바구니 담기·조회를 SDK 가 직접 처리하므로 위 콜백을 구현하지 않아도 됩니다.

방송 알림 신청

방송 예정(READY) 상태인 방송의 카드 제목 아래에 "알림신청" 버튼이 나타납니다. 버튼은 initPlugin() 설정으로 켜며, 모든 방송 예정 방송에 켜는 방법과 특정 방송에만 켜는 방법 두 가지가 있습니다.

방법 1 · 모든 방송 예정 방송에 노출

webClientSettings 안에 useRemind: true 한 줄을 넣습니다. 이후 새로 등록되는 방송 예정 방송에도 자동으로 적용됩니다.

cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  webClientSettings: {
    useRemind: true
  }
});
방법 2 · 특정 방송에만 노출

버튼을 보여줄 방송의 campaignId 를 문자열 배열로 넘깁니다. 목록에 없는 방송에는 버튼이 나타나지 않습니다. 넣는 값은 campaignKey 가 아니라 campaignId 입니다 — 가이드의 다른 곳에서 쓰는 방송 키와 다른 값이고, Admin 의 방송 상세에서 확인합니다.

cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  listCampaignIdsForRemindButton: ['2262194', '2262195']
});
설정 조합별 결과

두 설정은 함께 쓸 수 있고, useRemind 가 켜져 있으면 목록과 관계없이 전체에 노출됩니다.

useRemindlistCampaignIdsForRemindButton결과
true미지정방송 예정 전체 노출
미지정 또는 false지정목록에 있는 방송만 노출
true지정방송 예정 전체 노출
미지정 또는 false미지정노출 안 함
버튼 클릭 이벤트 받기

시청자가 버튼을 누르면 ON_CLICK_REMIND_CAMPAIGN 이 오고, payload.isReminded 로 신청·해제를 구분합니다. 이 콜백에서 고객사의 알림 예약 처리를 연결하세요.

ON_CLICK_REMIND_CAMPAIGN: function (payload) {
  const campaignKey = payload.campaign.campaignKey;
  if (payload.isReminded) {
    subscribeToMyPush(campaignKey);     // 알림 신청
  } else {
    unsubscribeFromMyPush(campaignKey); // 알림 해제
  }
}

방송 예정 상태에서만 표시됩니다. 방송이 시작되거나 끝나면 버튼은 자동으로 사라집니다. 코드 설정이 Admin 설정보다 우선합니다 — Admin 에서 알림 기능이 켜져 있어도 useRemind: false 를 넣으면 버튼이 꺼집니다. 신청 상태는 브라우저에 저장됩니다 — 신청 후에는 같은 브라우저에서 "신청완료" 로 표시되고, 다시 누르면 취소됩니다.

옥션

실시간 경매 기능입니다. 고객사가 반드시 처리해야 하는 것은 낙찰자를 결제로 보내는 것 하나이고, 나머지 이벤트는 자체 UI 를 만들거나 로그를 남길 때만 씁니다.

낙찰자를 결제로 보내기

경매가 끝나고 낙찰자에게 안내 시트가 뜹니다. 시청자가 그 시트의 결제 버튼을 누르면 ON_AUCTION_WINNER_CHECKOUT_CLICKED 가 옵니다.

ON_AUCTION_WINNER_CHECKOUT_CLICKED: function (payload) {
  // payload.auction.winnerCheckoutScheme 에 Admin 에서 설정한 결제 주소가 옵니다
  const scheme = payload.auction && payload.auction.winnerCheckoutScheme;
  location.href = scheme || ('/checkout?sku=' + payload.product.sku);
}
ON_CLICK_BUY_BUTTON 과 헷갈리지 마세요 ON_CLICK_BUY_BUTTON 은 상품 목록의 구매 버튼이고 payload 가 Goods 입니다. 낙찰자 안내 시트의 결제 버튼은 이 이벤트를 발생시키지 않습니다 — 옥션 결제를 ON_CLICK_BUY_BUTTON 으로만 처리하면 낙찰자가 버튼을 눌러도 아무 일도 일어나지 않습니다.
payload 구조

아래 옥션 이벤트들은 같은 모양의 payload 를 받습니다 — product(상품)와 auction(경매) 두 덩어리입니다.

위치주요 필드
payload.productproductId · sku · name · brand · originalPrice · discountPrice · currency · url · imageUrl · stockStatus · auctionStatus
payload.auctionwinnerCheckoutScheme(낙찰자 결제 주소) · winner({ name, uid, userId }) · leader(현재 최고 입찰자) · currentPrice · winningPrice · startingPrice · bidRound · bidStatus · endingAt · endedAt · recommendedPrices
payload.transactionId낙찰자 본인에게만, 거래 ID 가 있을 때 ON_AUCTION_WINNER_DETERMINED 에 함께 실립니다
경매 진행 상태 따라가기
이벤트언제
ON_AUCTION_READY경매가 준비됐을 때
ON_AUCTION_STARTED경매가 시작됐을 때
ON_BID_STATUS_CHANGED입찰 상황이 바뀔 때 (최고가·입찰자 갱신)
ON_BID_RESULT_RECEIVED내가 넣은 입찰의 결과가 왔을 때
ON_AUCTION_BID_TIME_EXTENDED막판 입찰로 종료 시간이 연장됐을 때
ON_AUCTION_ENDED경매가 끝났을 때
ON_AUCTION_WINNER_DETERMINED낙찰자가 정해졌을 때 (모든 시청자에게)
ON_AUCTION_SELF_WON내가 낙찰됐을 때 (낙찰자 본인에게만)
입찰 자격

시청자가 입찰을 시도했는데 자격이 없으면 CAN_BID_REQUIRED 가 { isLogin: true, canBid: false } 와 함께 옵니다. 게스트는 이 이벤트가 아니라 LOGIN_REQUIRED 가 옵니다 — 로그인부터 시켜야 하기 때문입니다.

입찰 자격은 canBid 로 정합니다 사용자 정보를 넘길 때 canBid 로 입찰 가능 여부를 지정합니다(미션 2). 다만 플랫폼 자동 연동을 쓰면 canBid: true 가 무조건 붙어 쇼핑몰에 로그인한 사람이면 누구나 입찰할 수 있게 됩니다. 등급·구매 이력으로 자격을 가려야 한다면 setUserObject() 를 직접 호출하세요.

B. PIP — 페이지를 옮겨도 방송을 계속 보여주기

시청자가 방송 중에 상품을 누르면 상품 페이지로 이동하면서 방송이 끊깁니다. PIP 는 페이지는 이동하되 영상만 작게 남겨 시청을 이어 줍니다.

Shoplive 에 요청해서 켜는 기능입니다 (ask@shoplive.cloud). 켜진 뒤에는 고객사 코드에 추가할 것이 없습니다 — 시청자가 상품을 누르면 SDK 가 알아서 PIP 로 전환합니다.

미션 1 방법 B 에서는 쓸 수 없습니다 PIP 는 고객사 페이지에 SDK 가 로드돼 있어야 동작합니다. 방법 B(링크로 열기)는 Shoplive 가 제공하는 페이지를 여는 것이라 SDK 가 고객사 페이지에 없으므로 PIP 가 켜지지 않습니다. 방법 A 또는 방법 C 를 쓰세요.

두 가지 방식

구분웹 PIP브라우저 PIP
어떻게 보이나고객사 페이지 위에 작은 플레이어가 뜹니다. 시청자가 드래그로 옮길 수 있고 모양이 Shoplive 플레이어와 이어집니다브라우저가 제공하는 기본 PIP 창입니다. 모양은 바꿀 수 없습니다
어디까지 따라가나같은 탭 안에서만 유지됩니다다른 탭·다른 사이트로 가도 따라갑니다
쓸 수 있는 조건이동할 주소가 고객사와 같은 도메인이어야 합니다 (웹 PIP 을 쓰기 전에 확인할 세 가지)시청자가 직접 누른 동작이어야 합니다 (브라우저 PIP 의 제약)
모바일에서고객사 페이지 위에 직접 그리는 방식이라 데스크톱·모바일이 같습니다약합니다 — Android 웹뷰에서는 제한적이고 인앱 브라우저에서는 동작하지 않습니다. 데스크톱 위주로 보세요
이런 경우에자사몰 안에서 상품 상세로 보낼 때 · 모바일 비중이 높을 때외부 도메인·다른 사이트로 보낼 때 · 데스크톱 비중이 높을 때

웹 PIP 을 쓰기 전에 확인할 세 가지

웹 PIP 은 이동할 페이지를 iframe 에 담아 화면 전체에 덮는 방식으로 동작합니다. 그래서 아래 중 하나라도 걸리면 켜도 화면이 그려지지 않습니다. 활성화를 요청하기 전에 먼저 확인하세요.

확인할 것가능불가
같은 도메인인가shop.com/live → shop.com/product/1shop.com/live → m.shop.com/product/1
서브도메인만 달라도 안 됩니다
X-Frame-Options 헤더자사 페이지에 SAMEORIGIN 으로 설정DENY 면 iframe 이 아예 그려지지 않습니다. 외부 사이트는 고객사가 바꿀 수 없습니다
Content-Security-Policyframe-src 에 이동할 주소를 허용허용 목록에 없으면 그려지지 않습니다 (FAQ · CSP)
상품 페이지가 iframe 안에서 열립니다 — 기존 기능이 그대로 되는지 확인하세요 같은 도메인이라 해도 iframe 안에서는 막히는 스크립트가 있습니다. 결제 모듈 · 외부 라이브러리 · 팝업 등 기존에 쓰던 기능이 정상 동작하는지 반드시 실제로 확인해 보세요. 이것이 웹 PIP 도입에서 가장 자주 걸리는 지점입니다.

결제 · 로그인 페이지에서는 자동으로 빠져나옵니다

결제처럼 iframe 안에서 진행되면 안 되는 페이지가 있습니다. 그래서 웹 PIP 은 아래 주소로 이동하는 순간 PIP 를 풀고 진짜 페이지 이동으로 전환합니다.

/orderform.html   /login.html   /basket.html   /navercheckout
고객사 결제 주소가 위와 다르면 알려 주세요 위 목록은 기본값입니다. 고객사 결제·로그인 페이지 주소가 다르다면 그 주소도 제외 목록에 넣어야 합니다 — 그대로 두면 결제가 iframe 안에서 진행됩니다. Shoplive 담당자에게 주소를 전달하면 추가할 수 있습니다.

브라우저 PIP 의 제약

  • 시청자가 직접 누른 동작에서만 켜집니다. 브라우저 보안 규칙이라 코드로 임의 호출할 수 없습니다.
  • PIP 전환과 새 창 열기를 동시에 할 수 없습니다. 둘 다 필요하면 PIP 로 바꾸는 동작을 먼저 하고, 새 창은 그 다음 동작으로 나누세요.
  • 모바일 대응이 약합니다. 데스크톱 브라우저에서는 대체로 잘 되지만, Android 웹뷰(고객사 앱 안의 웹 화면 · 카카오톡·인스타그램 같은 인앱 브라우저)에서는 제한적이거나 아예 동작하지 않습니다. 시청자 대부분이 모바일이거나 앱 웹뷰로 들어온다면 브라우저 PIP 만으로는 부족합니다 — 같은 도메인 안에서 움직이는 흐름은 웹 PIP 으로 덮고, 외부 도메인으로 나가는 흐름만 브라우저 PIP 에 맡기는 식으로 나누세요.

고객사 코드에서 직접 띄우기 선택

기본은 자동입니다 — 시청자가 플레이어 안에서 상품·링크를 누르면 SDK 가 알아서 PIP 로 전환합니다(미션 3 의 CLICK_PRODUCT · LINK 기본 동작). 고객사 UI 의 다른 버튼에서 같은 동작을 일으키려면 아래를 씁니다.

// 웹 PIP 로 전환하면서 지정한 주소로 이동합니다
cloud.shoplive.navigateWithPIP('/product/1', 'YOUR_CAMPAIGN_KEY');

웹 PIP 이 켜져 있지 않거나 해당 방송의 플레이어가 없으면 아무 일도 일어나지 않습니다. PIP 상태에서 시청자가 작은 플레이어를 누르면 PIP 가 해제되고 방송 화면으로 돌아갑니다.

C. SPA 에서 쓰기

  • initPlugin({ isSPA: true }) — 클라이언트 라우팅으로 페이지가 바뀌어도 SDK 를 유지합니다. initPlugin 은 한 페이지 수명에서 한 번만 동작하며 두 번째 호출은 무시됩니다.
  • 컴포넌트가 사라질 때 destroyPlayer(id) · destroyOverall(id, true) 로 정리하세요. 컨테이너 DOM 만 제거하면 SDK 내부에는 플레이어가 남습니다.
  • 자체 라우터가 URL 변화를 감시한다면, 모달이 주소를 건드리지 않도록 disableHistoryHook 을 켜세요. initPlugin({ disableHistoryHook: true }) 로 페이지 전체에 걸 수도 있습니다.

D. 고객사 앱 웹뷰 안에서 띄우기

방송을 붙인 웹 페이지를 고객사 앱의 웹뷰로 여는 경우(하이브리드 앱), 웹뷰 쪽 설정을 바꾸지 않으면 영상이 재생되지 않거나 전체 화면으로 튀어나갑니다. 아래는 앱 코드에서 한 번만 설정하면 됩니다.

플랫폼설정안 하면
iOS (WKWebView)configuration.allowsInlineMediaPlayback = true재생이 시작되는 순간 네이티브 전체 화면 플레이어로 전환돼 플레이어 UI 가 사라집니다
Android (WebView)settings.mediaPlaybackRequiresUserGesture = false시청자가 직접 탭하기 전까지 자동재생이 시작되지 않습니다
// iOS — WKWebView 를 만들 때
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: rect, configuration: configuration)
// Android — WebView 생성 후
webView.settings.mediaPlaybackRequiresUserGesture = false
모바일 앱에 방송을 넣는 방법은 두 가지입니다 이 부록은 이미 만든 웹 페이지를 앱 웹뷰로 여는 경우입니다. 앱 화면 자체에 방송을 넣으려면 웹 SDK 대신 iOS·Android 네이티브 SDK 가 따로 있으니 Shoplive 담당자에게 문의하세요.

E. RequireJS / AMD 환경

SDK 는 영상 재생에 hls.js 를 씁니다. 보통은 SDK 가 https://cdnjs.cloudflare.com 에서 <script> 태그로 직접 불러오지만(CSP 를 쓴다면 이 출처도 허용해야 합니다), 페이지가 RequireJS 로 모듈을 관리해 그 방식이 막힌다면 로더를 넘겨 대신 불러오게 합니다.

cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  useAMD: true,
  AMDLoader: require   // 생략하면 window.require 를 씁니다
});
  • 페이지에 이미 window.Hls 가 있으면 useAMD 는 아무 일도 하지 않습니다 — SDK 가 그 객체를 그대로 씁니다.
  • AMDLoader 를 생략하면 window.require 로 대신합니다. 둘 다 없으면 콘솔에 Invalid AMDLoader requirejs 를 남기고 중단하는데, 이때 영상이 재생되지 않습니다 — 재생이 안 되는데 다른 오류가 없다면 이 로그부터 확인하세요.
  • 일반 <script> 로드 환경에서는 쓸 일이 없습니다.

F. 숏폼 SDK 와 같은 페이지에서

라이브(shoplive.js)와 숏폼(shoplive-shortform.js)은 별도 스크립트이지만 같은 window.cloud.shoplive 객체에 함수를 추가하는 방식이라 한 페이지에 함께 로드할 수 있습니다. 로드 순서는 상관없습니다.

  • 초기화는 각각 호출합니다 — initPlugin() / initShortform()
  • 사용자 정보 함수(setUserObject · setUserJWT · logout)는 두 SDK 가 공유하므로 한 번만 호출하면 양쪽에 반영됩니다. 마지막에 호출한 값이 남습니다
  • 라이브 미니 프리뷰와 숏폼 프리뷰는 서로를 모릅니다. 둘 다 기본 위치가 화면 구석이라 같은 자리에 겹칠 수 있습니다 — 두 기능을 함께 쓴다면 한쪽 위치를 다른 모서리로 지정하세요. 라이브 쪽 위치는 미션 4 · 위치와 크기, 숏폼 쪽은 숏폼 가이드를 참고하세요

숏폼 연동 방법은 숏폼 가이드를 참고하세요.

G. 쇼핑몰 플랫폼별 설치 위치

Cafe24 · 고도몰 · WISA · 메이크샵 · 아임웹 · Shopify 로 만든 쇼핑몰에서 관리자 화면의 어디에 코드를 넣는지 정리했습니다. 넣는 코드는 미션 1(방송 목록 · 플레이어)과 미션 2 · 3단계(로그인 자동 연동)와 같고, 여기서는 위치와 플랫폼별 회원 변수만 다룹니다.

모든 플랫폼에 공통인 순서
  1. 운영 중인 스킨을 복사해 편집용 스킨에서 작업합니다 — 저장 즉시 실제 쇼핑몰에 반영되는 것을 막습니다
  2. 공통 레이아웃(모든 페이지가 함께 쓰는 상단 영역)에 SDK 로드 · 회원 정보 엘리먼트 · initPlugin() 을 넣습니다 — 아래 코드 ①
  3. 방송 목록을 보여 줄 페이지를 새로 만들고 setOverall() 을 넣습니다 — 아래 코드 ②
  4. 메뉴(진입점)에 그 페이지 주소를 연결합니다
  5. 진행 중인 방송이 있을 때 미니 프리뷰가 뜨는지 확인합니다. 안 뜨면 Admin 방송 상세에서 API · 플러그인 공개를 켭니다
PC 와 모바일 스킨이 따로인 플랫폼(Cafe24 · 고도몰 · WISA · 메이크샵)은 모바일 스킨에서 같은 순서를 한 번 더 합니다.

① 공통 레이아웃

<!-- 공통 레이아웃 — 모든 페이지에 한 번만 (위치는 플랫폼별 안내 참고) -->
<script src="https://static.shoplive.cloud/shoplive.js"></script>
<!-- 회원 정보 엘리먼트 — 플랫폼별 (아래 각 플랫폼 참고) -->
<script>
  cloud.shoplive.initPlugin({
    accessKey: 'YOUR_ACCESS_KEY',   // Admin 방송 상세에서 복사
    platform: 'CAFE24'              // 플랫폼에 맞는 값 (아임웹은 생략)
  });
</script>

② 방송 목록 페이지

<!-- 방송 목록 페이지 — 이 페이지의 HTML 영역에 -->
<div id="shoplive-overall"></div>
<script defer>cloud.shoplive.setOverall('shoplive-overall');</script>

Cafe24

로그인eclogin.cafe24.com/Shop
PC대표디자인을 복사 → 복사본 선택 후 수정 → 편집창 주요화면 › 메인화면 → 하단 HTML 영역의 <!--@layout(/layout/basic/layout.html)--> 로 공통 레이아웃을 열어 <body> 바로 아래에 ①
방송 목록 페이지왼쪽 아래 폴더추가로 live 폴더 → 화면추가로 live.html(화면분류: 선택안함(자동지정)) → HTML 영역의 기존 코드를 지우고 ②
진입점메뉴 탭에 마우스를 올려 편집 → <a href="/live/live.html"> 처럼 경로 입력
모바일모바일 대표디자인을 복사해 같은 순서
구매 전환스킨의 order_result.html </body> 직전 — 코드는 미션 6 · 플랫폼에서

회원 정보 엘리먼트

<div module="Layout_stateLogon" style="display:none;">{$id} {$name}</div>

고도몰

로그인nhn-commerce.com → 설치할 쇼핑몰 선택
PC관리자 › 디자인에서 스킨을 복사해 작업 스킨으로 설정(목록에 WORK 표시) → 전체 레이아웃 더블클릭 › 상단 레이아웃 → <body> 바로 아래에 ① → 디자인 페이지 저장
방송 목록 페이지넣을 폴더를 더블클릭 › 새로운 페이지 추가 → 파일명 입력 → 추가한 페이지에 ② → 저장
진입점페이지의 화면보기로 주소 복사 → 전체 레이아웃 › 상단 디자인 › 상단 기본파일의 탭에 붙여넣기
모바일모바일샵 디자인 스킨 리스트에서 같은 순서

회원 정보 엘리먼트

<div style="display:none;">
  <span id="cloud-shoplive-user-id">{=gSess.memId}</span>
  <span id="cloud-shoplive-user-name">{=gSess.memNm}</span>
</div>

WISA

로그인wisa.co.kr → 설치할 쇼핑몰
PC디자인 › 디자인관리 › 스킨 관리 — 사용 스킨이 아닌 편집 스킨에 적용되고 편집 스킨 미리보기로 확인합니다 → HTML 편집 › 상단공통페이지 편집의 맨 위에 ①
방송 목록 페이지HTML 편집 › 추가 페이지 편집 → 페이지 명 입력 → 내용편집 → 에디터를 HTML 로 전환 → ② → 확인
진입점페이지 파일명을 눌러 주소 복사 → 상단공통페이지 편집의 탭에 붙여넣기
모바일모바일 › 스킨 관리에서 같은 순서. 추가 페이지는 속성수정에서 모바일페이지 사용 = 사용함 으로 바꾼 뒤 모바일 추가 페이지로 전환해 입력

회원 정보 엘리먼트

<div style="display:none;">
  <span id="cloud-shoplive-user-id">{{$회원아이디}}</span>
  <span id="cloud-shoplive-user-name">{{$회원명}}</span>
</div>

메이크샵

주석 · 메이크샵 platform: 'MAKESHOP' 은 미션 2 · 3단계의 지원 값 목록에 없습니다. 숏폼 SDK 소스에서는 이 값의 처리가 비활성화(주석 처리)돼 있고, 라이브 SDK 지원 여부는 확인 중입니다. 아래는 이전 문서(Document360)의 방식이며, 로그인 이름이 채팅에 나오지 않으면 미션 2 · 2단계처럼 setUserObject() 를 직접 호출하세요.
로그인makeshop.co.kr
PC개별 디자인에서 설치할 스킨의 디자인 편집하기 → 기본 상단에 ①(회원 정보 · platform 은 아래 코드)
방송 목록 페이지방송 목록을 보여 줄 페이지에 ②
진입점② 를 넣은 페이지의 주소를 메뉴 경로로 지정
모바일모바일D4(개별 디자인) › HTML 디자인 편집하기 › 기본 상단에서 같은 순서

회원 정보 엘리먼트

<div style="display:none;">
  <span id="cloud-shoplive-user-id"><!--/user_id/--></span>
  <span id="cloud-shoplive-user-name"><!--/user_name/--></span>
</div>
<script>cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY', platform: 'MAKESHOP' });</script>

아임웹

주석 · 아임웹 아임웹은 platform 값이 없습니다. 아임웹이 로그인 회원에게 넣어 두는 window.MEMBER_UID 로 setUserObject() 를 부르는 방식(미션 2 · 2단계)이며, 이 변수는 아임웹 쪽 동작이라 쇼핑몰마다 값이 있는지 확인하세요.
위치아임웹 어드민 › 디자인 편집 › 공통 레이아웃의 <head> 에 아래 코드 — 모든 페이지에 들어가야 미니 프리뷰가 동작합니다
방송 목록 페이지방송 목록을 보여 줄 페이지의 HTML 위젯(또는 body 영역)에 ②
주의initPlugin() 이 한 페이지에 두 번 들어가지 않게 합니다. 서버가 필요한 JWT 보안 인증(미션 2)은 아임웹에서 쓸 수 없습니다
<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY' });
  if (window.MEMBER_UID) {            // imweb 이 로그인 회원에게 넣어 두는 회원 번호
    cloud.shoplive.setUserObject({ userId: window.MEMBER_UID });
  }
</script>

Shopify

Shopify 앱(ShopliveAssistant)으로 연결 — 권장

  1. Shopify 앱스토어의 ShopliveAssistant 페이지에서 오른쪽 위 계정 아이콘으로 연동할 스토어가 맞는지 확인합니다
  2. Install → 앱이 요청하는 권한(담당자 정보 · 상품 데이터 등)을 확인하고 설치를 마칩니다
  3. Shopify 관리자 Apps › Shoplive Assistant 에서 Shoplive 계정으로 로그인합니다
  4. 스토어에 연결된 Access Key 를 확인하고(편집 가능), 상품 동기화로 Shopify 상품을 Shoplive 로 가져옵니다 — 최근 변경분만 / 옵션을 개별 상품으로 중 선택
  5. 앱은 계정 연결과 상품 동기화까지입니다. 플레이어는 아래 테마 코드로 설치합니다
PC · 모바일 공통온라인 스토어 › 테마 › 현재 테마의 작업 › 코드 편집 → theme.liquid 의 <body> 바로 아래에 아래 코드
방송 목록 페이지온라인 스토어 › 페이지 › 페이지 추가 → 내용을 HTML 로 바꿔 아래 ② 코드
진입점온라인 스토어 › 탐색에서 메뉴에 그 페이지 추가
구매 전환설정 › 고객 이벤트 › 사용자 지정 픽셀 — 코드는 미션 6 · 플랫폼에서
장바구니platform: 'SHOPIFY' 면 플레이어 안 장바구니 담기 · 조회를 SDK 가 처리합니다 — A. 활성화가 필요한 기능

theme.liquid — 로그인 회원은 Liquid 변수로 넘깁니다

<script src="https://static.shoplive.cloud/shoplive.js"></script>
<script>
  cloud.shoplive.initPlugin({ accessKey: 'YOUR_ACCESS_KEY', platform: 'SHOPIFY' });
  {% if customer %}
  cloud.shoplive.setUserObject({ userId: "{{ customer.id }}", userName: "{{ customer.first_name }}" });
  {% endif %}
</script>
<div id="shoplive-overall"></div>
<script defer>
  document.addEventListener('DOMContentLoaded', function () {
    cloud.shoplive.setOverall('shoplive-overall');
  });
</script>

이전 ← 전체 예제다음 → 함수 색인

레퍼런스

함수 색인

cloud.shoplive.*에서 웹 연동에 쓰는 함수 전체입니다. 이름을 알고 있을 때 어느 절을 볼지 찾는 용도입니다.

묶음함수가이드
초기화initPlugin(config)빠른 시작 · 필드
플레이어setPlayer(containerId, config) · destroyPlayer(containerId)미션 1 · 방법 A · 필드
라이브 페이지 템플릿setOverall(containerId, config?) · destroyOverall(containerId?, removeActivePlayer?)미션 1 · 방법 C · 필드
모달showFeaturedPlayerModal(config) · dismissLayerModalPlayer()미션 1 · 모달로 띄우기
미니 프리뷰showLiveMiniPreview() · dismissLiveMiniPreview() · setOnClickLiveMiniPreview(fn)미션 4 · 미니 프리뷰
방송 카드 · 커버setLivePreview(containerId, config) · destroyLivePreview(containerId?) · setOnAirCover(containerId) · setLandscapeOnAirCover(containerId) · setFeaturedCampaign(containerId, config)미션 4 · 방송 카드
사용자setUserObject(user) · setUserJWT(jwt) · logout()미션 2
이벤트(initPlugin 옵션) messageCallback미션 3 · 이벤트 사전
플레이어 제어getPlayer(key?) · getActivePlayers() · player.sendMessage(action, payload)미션 5 · 명령 사전
PIPnavigateWithPIP(url, campaignKey?)부록 B · PIP
구매 전환sendConversionEvent(event)미션 6

이 표에 없는 이름이 cloud.shoplive에 보인다면 모바일 앱 SDK 연동용이거나 Shoplive 내부용입니다. 웹 연동에서는 사용하지 않습니다.

이전 ← 부록다음 → 설정 객체 필드 사전

레퍼런스

설정 객체 필드 사전

각 함수에 넘기는 설정 객체에 어떤 값을 넣을 수 있는지 모아 둔 목록입니다. accessKey와 campaignKey를 빼면 대부분 선택 항목이라, 필요한 것만 넣고 나머지는 비워 두면 기본값 또는 계정 설정값으로 동작합니다.

containerId는 방송 키가 아니라 고객사 페이지 DOM 요소의 id입니다 setPlayer · setOverall · setLivePreview · setOnAirCover 는 고객사가 페이지에 미리 만들어 둔 빈 요소 안에 화면을 그립니다. 그래서 첫 번째 인자는 그릴 자리인 <div>의 id 속성값입니다. CSS 선택자가 아니라 document.getElementById()로 찾으므로 #은 붙이지 않습니다. 그 id를 가진 요소를 찾지 못하면 콘솔에 Invalid containerId를 남기고 아무것도 그리지 않습니다.

초기화 — initPlugin(config)

필드필수설명
accessKey필수Shoplive에서 발급한 고객사 식별 키
messageCallback선택이벤트별 동작을 정의하는 함수 맵 (미션 3)
userObject / userJWT선택로그인 사용자 정보. 이후 setUserObject / setUserJWT로 변경 가능 (미션 2)
platform선택쇼핑몰의 로그인 상태를 SDK가 읽어 가게 합니다 — 'CAFE24' · 'WISA' · 'GODOMALL' · 'SHOPIFY' (미션 2 · 3단계)
shareUrl선택공유 버튼이 내보낼 주소. 지정하지 않으면 현재 페이지 주소 (미션 4 · 공유 URL)
useAutoPreview선택false면 초기화 시 미니 프리뷰를 자동으로 띄우지 않습니다 (미션 4)
isSPA선택true면 페이지 전환 시 SDK를 유지 (부록 C)
disableHistoryHook선택true면 모달이 열릴 때 주소를 건드리지 않습니다 (부록 C)
listCampaignIdsForRemindButton선택알림 신청 버튼을 띄울 방송의 campaignId 목록 (부록 A)
webClientSettings.useRemind선택true면 방송 예정인 모든 방송에 알림 신청 버튼을 띄웁니다. Admin 설정보다 우선합니다 (부록 A)
anonId선택고객사가 관리하는 비로그인 식별자. 전환 집계에 함께 쓰입니다. ?anonId= 쿼리로 넘겨도 인식됩니다 (미션 6)
gaId선택Google Analytics 클라이언트 ID. Shoplive 기록과 GA 데이터를 같은 사용자로 맞출 때
customerDeviceId선택고객사가 쓰는 기기 식별 ID. SDK가 보내는 요청에 함께 실립니다
useAMD / AMDLoader선택RequireJS 환경 (부록 E)

여기에 없는 값을 코드에서 보셨다면. 모바일 앱 웹뷰에서 SDK를 띄울 때 앱이 넘기는 값(광고 식별자 등)이거나, 특정 고객사와 개별 협의된 값, 또는 Shoplive 개발용 값입니다. 웹 페이지에 직접 연동할 때는 쓰지 않습니다.

플레이어 — setPlayer(containerId, config)

필드기본값설명
campaignKey필수재생할 방송 키. 없으면 콘솔에 Invalid AccessKey or CampaignKey
ui.viewerCounttrue시청자 수 표시
ui.likeCounttrue좋아요 수 표시
ui.backButtonfalse뒤로가기 버튼 표시
ui.shareButtonfalse공유 버튼 표시
isFullScreenfalse항상 전체 화면으로 실행 (viewport meta 필요)
hideControlsfalse컨트롤 UI를 숨긴 채 시작
playerAlias—getPlayer('별명')으로 집어낼 별명 (미션 5)
closePlayerAfterCampaignEnd—방송이 끝나고 이 시간(ms)이 지나면 CLOSE_PLAYER 이벤트가 옵니다. SDK 는 이 이벤트로 아무것도 하지 않으므로 플레이어를 실제로 치우려면 고객사가 destroyPlayer() 또는 dismissLayerModalPlayer() 를 직접 호출해야 합니다.
지정하지 않으면 방송 종료 30초 뒤 플레이어가 스스로 새로고침됩니다(종료 화면으로 정리)
chatFilterPatterns—정규식 문자열 배열. 일치하는 채팅은 전송되지 않고 ON_FILTERED_CHAT 발생
passcode—리허설 방송 입장 코드
brand—브랜드 좋아요 초기 상태 — { identifier, favorite } (부록 A)

라이브 페이지 템플릿 — setOverall(containerId, config)

필드기본값설명
featuredOnlyfalse대표 방송(플레이어) 영역만 노출
featured.fixedCampaignKey—대표 방송을 특정 방송으로 고정
campaignListsOnlyfalse방송 목록 영역만 노출
campaignStatus전체'READY' · 'ONAIR' · 'CLOSED'. 문자열 또는 배열(적은 순서대로 정렬)
tags / tagSearchOperator—태그 필터 · 'OR' | 'AND'
displayTags—목록 위에 필터 탭으로 노출할 태그
skus / goodsIds—특정 상품이 포함된 방송만 — 고객사 상품코드 / Shoplive 내부 상품 ID
campaignList.layout'GRID''GRID' · 'HORIZONTAL'
campaignList.type—'SECTION' — 상태별 섹션으로 나눠 표시
hasMoreButtontruefalse면 'GRID'에서 무한 스크롤
ascending상태에 따라정렬 방향. 미지정 시 campaignStatus가 'READY' 문자열 하나일 때만 오름차순
sectionTitles기본 문구{ onair, ready, closed, all }
disableHistoryHookfalse목록에서 모달을 열 때 주소를 건드리지 않음

그 밖의 함수

함수설정 객체
showFeaturedPlayerModal(config)campaignKey (필수) · disableHistoryHook
setLivePreview(containerId, config)campaignKey (필수) · onClick(campaign)
setFeaturedCampaign(containerId, config)campaignKey (필수) · playerContainerId · playerAlias
sendConversionEvent(event)accessKey · eventType (둘 다 필수) · orderId · products[] · timestamp · custom · userObject / userJWT (미션 6)

사용자 정보 — setUserObject(user)

userId 외에는 모두 선택입니다. JWT 인증의 클레임 이름은 미션 2 · 5단계를 참고하세요(채팅 표시 이름이 userName이 아니라 name입니다).

필드설명
userId필수. 로그인 사용자의 고유 ID. 빈 값이면 게스트로 처리됩니다
userName채팅에 표시될 이름. 금칙어는 *로 대체되어 표시됩니다
gender · age통계용. 'm' / 'f', 나이는 5세 단위로 그룹화되어 집계됩니다
userScore-100 ~ 100 정수. 입장 제한·이벤트 추첨 제한의 기준으로 쓸 수 있습니다
userType입장 제한(Entry Type)이 걸린 방송에서 참조하는 값 (예: 'VIP')
profile채팅 목록에 표시할 프로필 이미지 URL. 권장 64×64px
custom자유 형식 데이터, 최대 1KB
canBidtrue / false. 경매 입찰 자격 (부록 A · 옥션). 플랫폼 자동 연동은 이 값을 항상 true 로 넣습니다

이전 ← 함수 색인다음 → 이벤트 사전

레퍼런스

이벤트 사전

initPlugin({ messageCallback })에 등록할 수 있는 이벤트 전체입니다. 쓰는 법과 예제는 미션 3에 있고, 여기서는 이름으로 빠르게 찾는 용도입니다. 화면 열의 번호는 화면 용어 가이드의 그림과 대응합니다.

기본 동작이 있는 이벤트 — 등록하면 대체됩니다

등록하지 않아도 SDK가 알아서 처리합니다. 등록하는 순간 기본 동작은 더 이상 실행되지 않으므로, 로깅만 하려던 경우에도 원래 동작을 직접 다시 구현해야 합니다.

이벤트기본 동작화면
CLICK_PRODUCT새 창으로 payload.url 이동⑥
LINK현재 창에서 payload.url 이동 (웹 PIP 켜짐 시 플레이어 유지)⑨ ⑩ ⑪
LINK_NEW_WINDOW새 창으로 payload.url 이동⑨ ⑩ ⑪
CLICK_SHARE_BTNOS 공유 시트(navigator.share) → 없으면 클립보드 복사 (공유 URL)⑫
CLICK_BACK_BUTTONhistory.back()⑫
ON_CLICK_CAMPAIGN_LIST_ITEM모달 플레이어 열기—
ON_CLICK_CLOSE_PLAYER_MODAL모달 플레이어 닫기—
ON_CLICK_SCHEME_BUTTON새 창으로 payload.scheme 열기. payload.schemeType 은 GO_PRODUCT · ADD_PRODUCT · REMOVE_PRODUCT · WINNER_CHECKOUT⑥
REQUEST_FULL_SCREEN · EXIT_FULL_SCREEN브라우저 전체 화면 진입 · 해제①

기본 동작이 없는 이벤트 — 등록해야 무언가 일어납니다

이벤트언제화면
DOWNLOAD_COUPON클릭 동작이 "쿠폰 다운로드"인 팝업을 눌렀을 때. 등록하지 않으면 아무 일도 일어나지 않습니다⑨ ⑩ ⑪
CUSTOM_ACTION클릭 동작이 "사용자 정의"인 팝업을 눌렀을 때⑨ ⑩ ⑪
LOGIN_REQUIRED게스트가 로그인이 필요한 기능에 접근했을 때⑦

이 세 가지는 Shoplive Admin 의 팝업 설정에서 고른 클릭 동작이 그대로 이벤트 이름이 되어 전달됩니다. 설정한 동작과 등록한 이벤트 이름이 어긋나면 콜백이 호출되지 않으니, 미션 3의 Admin 설정 표를 함께 보세요.

알림만 받는 이벤트

ON_PLUGIN_INITIALIZED · ON_PLAYER_STARTED · NOW_ONAIR_CHECKED · ON_CAMPAIGN_STATUS_CHANGED · ON_CHANGED_VIEWER_STAT · ON_PLAYER_MODAL_SHOWN · ON_PLAYER_MODAL_HIDDEN · ON_LIVE_MINI_PREVIEW_SHOWN · ON_LIVE_MINI_PREVIEW_HIDDEN · ON_CLICK_CLOSE_LIVE_MINI_PREVIEW · ON_PRODUCT_LIST_SHOWN · ON_CHANGED_CAMPAIGN_PRODUCT_LIST · ON_FILTERED_CHAT · ON_RECEIVED_CHAT · ON_ERASED_CHAT · ON_CHAT_INPUT_FOCUS_IN · ON_CHAT_INPUT_FOCUS_OUT · ON_UID_ISSUED — 발생 시점과 payload 는 미션 3 · 알림만 받는 이벤트에 있습니다.

명령에 대한 응답으로만 오는 이벤트 — CURRENT_PLAYBACK_TIME 은 GET_CURRENT_PLAYBACK_TIME 을 보냈을 때만 옵니다. 스스로 발생하지 않습니다.

활성화가 필요한 기능의 이벤트

기능이벤트
브랜드 좋아요ON_CHANGED_BRAND_FAVORITE · ON_CLICK_BRAND_FAVORITE_BUTTON
장바구니CLICK_PRODUCT_CART · (Shopify) CLICK_SHOPIFY_CART · ADD_TO_SHOPIFY_CART · REMOVE_TO_SHOPIFY_CART · REDIRECT_TO_SHOPIFY_CART_PAGE
방송 알림 신청ON_CLICK_REMIND_CAMPAIGN
옥션ON_AUCTION_WINNER_CHECKOUT_CLICKED · ON_AUCTION_READY · ON_AUCTION_STARTED · ON_AUCTION_ENDED · ON_BID_STATUS_CHANGED · ON_BID_RESULT_RECEIVED · ON_AUCTION_BID_TIME_EXTENDED · ON_AUCTION_WINNER_DETERMINED · ON_AUCTION_SELF_WON · CAN_BID_REQUIRED
ON_CLICK_BUY_BUTTON 은 옥션 전용이 아니라 상품 목록의 구매 버튼입니다

행동 로그 — EVENT_LOG

플레이어 안의 버튼 클릭·화면 노출을 하나의 이벤트로 모아 받습니다. 이름 51개 전체 목록은 미션 3 · 사용자 행동 로깅에 있습니다.

이 문서에 없는 이벤트 이름이 들어온다면 모바일 SDK 웹뷰나 Shoplive 운영 도구가 쓰는 내부 이벤트이거나, 특정 고객사와 개별 협의된 기능의 이벤트입니다. 일반 웹 연동에서는 처리할 필요가 없습니다.

이전 ← 설정 객체 필드 사전다음 → 플레이어 명령 사전

레퍼런스

플레이어 명령 사전

getPlayer().sendMessage(명령, payload) 로 보낼 수 있는 명령입니다. 쓰는 법은 미션 5에 있습니다. 화면 열의 번호는 화면 용어 가이드의 그림과 대응합니다.

명령payload동작화면
removeCoupon없음쿠폰 팝업 제거⑨
removeBanner없음배너 팝업 제거⑪
removeNotice없음공지 팝업 제거⑩
mute · unmute없음음소거 켜기 · 끄기⑧
playVideo · pauseVideo없음재생 · 일시정지①
videoRefresh없음영상 다시 불러오기①
hideControls · showControls없음컨트롤 UI 숨기기 · 보이기⑦
dismissGoodsList없음상품 목록 팝업 닫기⑥
showDialog{ title, message } + cancelText · confirmText · confirmTextColor · confirmBackgroundColor안내 창 띄우기 (미션 5)—
showToast{ message } + duration · position토스트 메시지 띄우기 (미션 5)—
GET_CURRENT_PLAYBACK_TIME없음현재 재생 위치 묻기 → CURRENT_PLAYBACK_TIME 이벤트로 응답 (미션 5)—
SEND_COMMAND_MESSAGE{ command, payload }아래 하위 명령을 전달하는 봉투—

SEND_COMMAND_MESSAGE 하위 명령

활성화가 필요한 기능에서 상태를 되돌려 줄 때 씁니다.

commandpayload쓰는 곳
SET_BRAND_FAVORITE{ identifier, favorite }브랜드 좋아요 — 현재 사용자의 좋아요 상태를 알려 줍니다
SET_PRODUCT_CART_ON{ products: [{ sku, isCartOn }] }장바구니 — 담긴 상태를 알려 버튼 모양을 바꿉니다
const player = cloud.shoplive.getPlayer();

player.sendMessage('SEND_COMMAND_MESSAGE', {
  command: 'SET_PRODUCT_CART_ON',
  payload: { products: [{ sku: 'PRODUCT_SKU', isCartOn: true }] }
});

이 표에 없는 명령 이름을 코드에서 보셨다면 모바일 SDK 웹뷰나 Shoplive 운영 도구가 쓰는 내부 명령입니다. 웹 연동에서는 쓰지 않습니다.

이전 ← 이벤트 사전다음 → 트러블슈팅 & FAQ

레퍼런스

트러블슈팅 & FAQ

플레이어가 뜨지 않아요

브라우저 콘솔을 먼저 확인하세요. 아래 경고가 뜬다면 원인이 바로 나옵니다.

콘솔 메시지원인
Invalid containerId컨테이너 div의 id가 setPlayer에 넘긴 값과 일치하지 않음
Invalid AccessKey or CampaignKeysetPlayer 에 넘긴 campaignKey가 비어 있거나 누락됨
Invalid AccessKeysetOverall · setLivePreview 등에서 accessKey를 찾지 못함. initPlugin 에서 받은 값도, 함수에 직접 넘긴 값도 없는 경우입니다
Empty AccessKeyinitPlugin 에 accessKey가 비어 있음
Invalid WebClientSettingsaccessKey가 잘못됐거나 초기 설정 조회 실패 — 아래 주의 참고
Invalid WebClientSettings 가 보이면 그 뒤의 호출이 전부 사라집니다 초기화는 설정을 서버에서 받아온 뒤에 완료됩니다. 그 전에 부른 setPlayer · setOverall · sendConversionEvent 는 일단 대기열에 쌓였다가 초기화가 끝나는 순간 한꺼번에 실행되는데, 설정 조회가 실패하면 초기화가 끝나지 않아 대기열이 영영 실행되지 않습니다. 자동 재시도도 없습니다.
그래서 증상은 "아무 오류도 없이 아무 일도 안 일어남" 으로 보입니다. 다른 경고가 없는데 플레이어도 전환 이벤트도 동작하지 않는다면 이 줄이 콘솔에 있는지부터 확인하세요. accessKey 오타와 네트워크·방화벽 차단이 주된 원인입니다.

콘솔에 경고가 없다면 해당 방송이 Admin에서 공개 설정됐는지 확인하세요 — 신규 방송은 기본값이 비공개라, 플러그인·API 공개 설정을 켜지 않으면 campaignKey가 맞아도 플레이어가 뜨지 않습니다.

참고로 initPlugin()보다 setPlayer()를 먼저 호출해도(순서가 바뀌어도) 내부적으로 대기 후 자동 처리되므로, 스크립트 순서 자체가 원인인 경우는 드뭅니다.

상품을 클릭했는데 아무 일도 안 일어나요

CLICK_PRODUCT를 재정의하지 않으면 기본적으로 새창으로 상품 URL이 열립니다. 현재 창에서 이동시키고 싶다면 콜백을 재정의하세요 (미션 3).

플레이어 내부(채팅창·상품 목록 등) 위치를 바꿀 수 있나요?

단순 위치 조정을 넘어, 고객사의 BI·CI와 스타일 가이드를 반영한 전용 디자인으로 커스텀 개발도 가능합니다. 필요하면 Shoplive 담당자에게 문의하세요.

다만 고객사 쪽 코드로 직접 조정하는 건 안 됩니다. 아래처럼 Player는 iframe 안에서 별도 페이지로 동작해서, 페이지에 적용한 CSS가 내부에는 적용되지 않기 때문입니다.

고객사 웹 페이지
  └─ Player SDK (스크립트)
        └─ <iframe>
              └─ Shoplive Player (별도 페이지로 동작)

플레이어를 담는 컨테이너(div)의 위치·크기는 페이지 CSS로 직접 조정할 수 있지만, iframe 내부(채팅창·상품 목록 등)는 그 경계 안에서 Shoplive Player 자체 레이아웃을 따릅니다.

미니 프리뷰 배너가 원치 않는 페이지에도 떠요

방송이 진행 중이면 initPlugin() 을 부른 모든 페이지에 자동으로 뜨는 것이 기본 동작입니다. 뜰 페이지를 고르려면 초기화에서 자동 노출을 끄고 원하는 페이지에서만 직접 부르세요.

cloud.shoplive.initPlugin({
  accessKey: 'YOUR_ACCESS_KEY',
  useAutoPreview: false      // 자동 노출 끄기
});

// 띄우고 싶은 페이지에서만
cloud.shoplive.showLiveMiniPreview();

자세한 조건과 노출 위치는 미션 4 · 미니 프리뷰에 있습니다.

같은 위치에 다른 방송으로 다시 setPlayer를 호출해도 되나요?

네. 같은 컨테이너 id로 setPlayer를 다시 호출하면 기존 플레이어를 정리하고 새 플레이어로 교체합니다. React 등에서 페이지 이동 없이 컴포넌트가 다시 렌더링되는 상황에도 안전하게 재호출할 수 있습니다.

다만 컴포넌트가 화면에서 완전히 사라지는(컨테이너까지 제거되는) 경우는 다릅니다. SDK가 이 시점을 자동으로 감지하지 못해 내부적으로 플레이어가 계속 활성 상태로 남고, 이 때문에 미니 프리뷰가 정상적으로 뜨지 않을 수 있습니다. 컨테이너를 제거하기 전에 cloud.shoplive.destroyPlayer("컨테이너id")를 먼저 호출하세요.

방송이 끝나면 다시보기(리플레이)는 어떻게 열어요? campaignKey를 바꿔야 하나요?

아니요, campaignKey는 그대로 사용합니다. 같은 캠페인이 ONAIR → CLOSED로 상태만 바뀌는 것이고, Admin에서 해당 방송의 "리플레이 공개" 설정이 켜져 있어야 종료 후에도 같은 campaignKey로 다시보기가 정상 재생됩니다.

영상이 자동으로 재생되지 않고 정지 화면만 보여요

브라우저가 소리 있는 영상의 자동재생을 막기 때문입니다. SDK는 음소거 상태로 재생을 시도하지만, 기기·설정에 따라 그마저 막히면 시청자가 한 번 탭해야 시작됩니다. 코드로 우회할 수 있는 영역이 아닙니다.

iOS에서 특정 기기만 정지 화면이라면 그 기기의 설정 → 손쉬운 사용 → 동작 → "비디오 미리 보기 자동 재생"이 꺼져 있는지 확인하세요. 저전력 모드·데이터 절약 모드도 같은 결과를 냅니다.

고객사 앱의 웹뷰로 연 페이지라면 원인이 다릅니다 — 웹뷰 설정 때문이므로 앱 코드에서 고칠 수 있습니다. 부록 D를 보세요.

CSP(Content-Security-Policy)를 쓰고 있어요. 어떤 도메인을 허용해야 하나요?

플레이어는 iframe 안에서 별도 페이지로 동작합니다. 그래서 고객사 페이지의 CSP가 관여하는 것은 SDK 스크립트와 iframe 을 띄우는 데까지이고, 영상·채팅처럼 iframe 안에서 일어나는 통신은 고객사 CSP의 적용 대상이 아닙니다.

지시어허용할 출처용도
script-srchttps://static.shoplive.cloud
https://cdnjs.cloudflare.com
SDK 본체 · hls.js — SDK 가 영상 재생용 hls.js 를 고객사 페이지에 직접 붙이므로 Shoplive 도메인이 아닌 이 출처도 필요합니다. 빠뜨리면 SDK 는 뜨는데 영상만 재생되지 않습니다
style-srchttps://static.shoplive.cloud · 'unsafe-inline'SDK 스타일시트 · 계정 커스텀 스타일(인라인)
frame-srchttps://static.shoplive.cloud플레이어 iframe — 빠뜨리면 플레이어 자리가 빈 채로 남습니다
connect-srchttps://config.shoplive.cloud · https://capi.shoplive.cloud · https://api.shoplive.cloud · https://*.shoplive.cloud고객사 설정 · 방송 목록 API · 전환 이벤트 · 이벤트 로그
img-srchttps://image.shoplive.cloud · https://*.shoplive.cloud포스터 · 썸네일 · 프로필 이미지

정확한 API 호스트는 계정 설정에 따라 다를 수 있으니 배포 전 네트워크 탭으로 확인하세요.

외부 스크립트를 CDN 에서 받는 것이 정책상 막혀 있다면, 페이지에서 hls.js 를 먼저 로드해 window.Hls 를 만들어 두면 SDK 가 그 객체를 그대로 씁니다 (부록 E).

플레이어 UI 언어를 바꿀 수 있나요?

플레이어 안의 문구는 시청자 브라우저의 언어 설정(navigator.language)을 따릅니다. 19개 언어를 지원하며, 코드에서 강제로 지정하는 공개 옵션은 없습니다.

특정 언어로 고정해야 한다면 Shoplive 담당자에게 문의하세요 — 계정·테마 설정으로 다룰 수 있는 경우가 있습니다.

initPlugin 을 두 번 불렀더니 두 번째 설정이 반영되지 않아요

초기화는 페이지당 한 번만 처리됩니다. 두 번째 initPlugin() 호출은 경고 없이 조용히 무시되고, 거기 담아 보낸 accessKey · messageCallback · useAutoPreview 도 함께 버려집니다.

React 의 StrictMode 처럼 컴포넌트가 두 번 마운트되는 환경이나, SPA 에서 라우팅할 때마다 초기화를 부르는 구조에서 자주 밟습니다. 초기화는 앱 진입점에서 한 번만 부르고, 페이지마다 달라지는 것은 setPlayer 쪽에서 처리하세요 (부록 C).

콜백을 나중에 바꾸고 싶다면 초기화를 다시 부르는 대신 setPlayer 의 messageCallback 을 쓰세요 — 이쪽은 방송마다 따로 줄 수 있고, 초기화 때 준 것보다 우선합니다.

PIP 가 동작하지 않아요

PIP 는 두 가지가 있고 원인이 서로 다릅니다. 부록 B에 조건이 정리돼 있으니 먼저 어느 쪽인지 확인하세요.

둘 다 계정에서 쓸 수 있도록 설정돼 있어야 하니, 조건을 다 맞췄는데도 안 되면 Shoplive 담당자에게 문의하세요.

구매 전환이 잡히지 않아요

sendConversionEvent() 도 초기화가 끝나기 전에 부르면 대기열에 쌓였다가 초기화 완료 시점에 실행됩니다. 그래서 초기화가 실패하면 전환 이벤트도 함께 사라집니다 — 콘솔에 Invalid WebClientSettings 가 있는지부터 확인하세요 (위 플레이어가 뜨지 않아요 참고).

결제 완료 페이지처럼 플레이어가 없는 페이지에서도 initPlugin() 을 먼저 불러야 전환이 전송됩니다. 보내는 위치와 필드는 미션 6, 시청 기록과 이어 붙는 기준은 3단계에 있습니다.

TypeScript 프로젝트인데 타입 정의가 있나요?

공식적으로 제공되는 타입 정의(.d.ts)는 없습니다. window.cloud.shoplive를 any로 선언하거나, 이 문서에 나온 필드 기준으로 필요한 만큼만 직접 타입을 선언해서 사용하세요.

이 외에 궁금한 점이 있다면 사이드바의 검색(또는 Ctrl+K / Cmd+K)으로 이 가이드 안을 찾아볼 수 있습니다. 검색으로도 해결되지 않는다면 Shoplive 담당자에게 문의해주세요.