Shoplive Web Player SDK 연동 가이드
라이브 커머스 방송을 웹 페이지에 연동하는 방법을 미션으로 나누어 안내합니다. 미션 1 만 하면 방송은 뜹니다 — 나머지는 필요한 것만 고르세요.
개요 & 요구사항
Shoplive Web Player SDK는 스크립트 태그 하나로 라이브 방송 플레이어를 웹 페이지에 연결합니다. 별도 패키지 설치나 빌드 도구 설정이 필요 없습니다.
원하는 위치에 플레이어를 직접 심거나, 링크 하나로 바로 열거나, 다른 페이지에 미리보기를 띄우는 것까지 — 필요한 방식만 골라 적용할 수 있습니다.
필요한 것
| 항목 | 확인 방법 |
|---|---|
| Access Key | Shoplive Admin — 방송 상세정보 페이지 상단 요약 카드 |
| Campaign Key | Shoplive Admin — 방송 상세정보 페이지 상단 요약 카드 |

무엇을 구현하고 싶나요?
미션 1 만 끝내면 화면에 방송이 뜹니다. 미션 2~6 은 서로 독립이라 순서와 상관없이, 필요한 것만 골라 적용하면 됩니다 — 전부 해야 하는 목록이 아닙니다.
| 구현하고 싶은 것 | 어디로 | |
|---|---|---|
| 페이지의 특정 위치에 플레이어를 붙인다 · 방송 목록까지 한 번에 노출한다 · 링크 하나로 방송을 연다 | 먼저 | 미션 1 · 방송 노출하기 |
| 채팅 이름 표시·입장 제한 등 사용자 정보를 연결한다 | 선택 | 미션 2 · 사용자 정보 연결 |
| 쿠폰·상품 클릭, 로그인 필요 등 사용자 상호작용에 고객사 코드를 실행하고 싶다 | 선택 | 미션 3 · 사용자 상호작용 처리하기 |
| 방송 페이지가 아닌 다른 페이지에서도 진행 중 방송을 알리고, 공유 링크로 들어오게 한다 | 선택 | 미션 4 · 다른 페이지에도 방송 알리기 |
| 떠 있는 플레이어를 고객사 코드에서 조작한다 (쿠폰 지우기·음소거·안내창) | 선택 | 미션 5 · 플레이어 제어하기 |
| 어느 채널로 들어왔고 실제로 구매했는지 집계한다 | 선택 | 미션 6 · 측정 — 유입과 구매 |
| 브랜드 좋아요·장바구니·알림 신청·옥션 등 활성화가 필요한 기능을 붙인다 | 선택 | 부록 A · 활성화가 필요한 기능 |
| 방송을 보다 상품 페이지로 옮겨도 영상이 끊기지 않게 한다 | 선택 | 부록 B · PIP |
| SPA · 앱 웹뷰 · RequireJS · 숏폼과 같은 페이지 등 특수한 환경에 맞춘다 | 선택 | 부록 C~F |
플레이어 화면의 각 요소를 뭐라고 부르는지는 화면 용어 가이드에 정리돼 있습니다 — 미션을 읽다 removeBanner 가 무엇을 지우는지 헷갈릴 때 돌아오세요.
빠른 시작
가장 짧은 경로로 화면에 플레이어를 띄우는 예시입니다. 세부 옵션은 아래 미션 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 가 무엇을 지우는 명령인지 헷갈릴 때 이 표로 돌아오세요.
플레이어 화면
| 번호 | 요소 | 노출 조건 | 웹에서 관련된 것 |
|---|---|---|---|
| ① | 영상 영역 | 상시 | 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 · 방송 노출하기
노출 방식은 세 가지입니다. 하나만 고르면 되고, 셋 다 같은 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 는 그대로 두면 됩니다 — 같은 키로 다시보기가 재생됩니다.
div 하나뿐입니다. 스크롤해도 플레이어가 따라붙는 것은 그 div 에 position:sticky 를 준 것으로, 페이지 CSS 로 자유롭게 잡을 수 있다는 뜻입니다. 영상 속 쇼핑몰은 촬영용 예시이며 문구는 영문입니다.화면에 무엇을 보일지 선택
| 옵션 | 기본값 | 설명 |
|---|---|---|
ui.viewerCount | true | 시청자 수 표시 |
ui.likeCount | true | 좋아요 수 표시 |
ui.backButton | false | 뒤로가기 버튼 표시. 누르면 CLICK_BACK_BUTTON 이 발생하고 기본 동작은 history.back() (미션 3) |
ui.shareButton | false | 공유 버튼 표시 (공유 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
});
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">
그 밖의 옵션 선택
특정 상황에서만 쓰는 값들입니다. 평소에는 지정하지 않아도 됩니다.
| 옵션 | 타입 | 설명 |
|---|---|---|
hideControls | boolean | true 면 플레이어의 컨트롤 UI 를 숨긴 채로 시작합니다. 시작 후에는 플레이어 제어의 showControls / hideControls 명령으로도 바꿀 수 있습니다 |
playerAlias | string | 이 플레이어에 붙이는 별명. 한 페이지에 플레이어가 여러 개일 때 getPlayer('별명') 으로 정확히 집어내기 위해 씁니다 (미션 5). 지정하지 않으면 campaignKey 로 찾습니다 |
closePlayerAfterCampaignEnd | number (ms) | 방송이 끝난 뒤 이 시간만큼 지나면 CLOSE_PLAYER 이벤트를 보냅니다. 방송이 끝났을 때 페이지를 정리하거나 다른 화면으로 보내는 신호로 쓰는 값입니다 — SDK 가 이 이벤트로 플레이어를 닫지는 않으므로 실제로 치우려면 destroyPlayer() 를 직접 불러야 합니다.지정하지 않으면 방송 종료 30초 뒤 플레이어가 스스로 새로고침해 종료 화면으로 정리됩니다 |
chatFilterPatterns | string[] | 정규식 문자열 배열. 시청자가 보내려는 채팅이 하나라도 일치하면 전송하지 않고 ON_FILTERED_CHAT 이벤트를 대신 발생시킵니다. 자체 금칙어 정책을 코드로 얹을 때 씁니다 |
passcode | string | 리허설 방송을 미리 보기 위한 입장 코드. 리허설로 설정된 방송은 ONAIR 가 되기 전까지 이 값이 있어야 입장됩니다 |
brand | object | 브랜드 좋아요 기능을 쓸 때 초기 상태를 넘깁니다 — { 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 · userNamegender · age · userScore | 선택 | tk 대신 쓰는 간이 방식. 이 다섯 개만 지원하며 tk 를 함께 주면 tk 가 우선합니다 (미션 2) |
backButton | 선택 | 1(표시) / 0(숨김) |
shareButton | 선택 | 1(표시) / 0(숨김) |
공유 버튼 클릭 시 전달할 페이지 URL을 지정하는 shareUrl 옵션(방법 A 참고)은 initPlugin()을 직접 호출해야 적용되므로, 방법 B에는 사용할 수 없습니다. 방법 B에서는 shareButton=0으로 공유 버튼 자체를 숨기는 것만 가능합니다.
messageCallback 도 등록할 수 없습니다.방법 C라이브 페이지 템플릿 — 플레이어와 방송 목록을 한 번에
플레이어 하나가 아니라 라이브 코너 페이지 전체가 필요할 때 씁니다. 빈 div 하나에 setOverall 을 호출하면 그 안에 대표 방송(진행 중이거나 가장 최근 방송) + 방송 목록이 Shoplive 템플릿으로 그려집니다. 어떤 방송을 띄울지 코드로 정하지 않아도 되므로, 방송이 계속 바뀌는 상설 코너에 적합합니다.
<div id="shoplive-overall-area"></div>
<script>
cloud.shoplive.setOverall('shoplive-overall-area');
</script>
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' 을 주면 상태별 섹션으로 나눠 표시합니다 |
hasMoreButton | true | 목록 아래 더보기 버튼. 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>
showFeaturedPlayerModal({ campaignKey, disableHistoryHook: true }) 로 끌 수 있습니다 — 대신 뒤로가기가 이전 페이지로 나갑니다.완료 확인
- (방법 A) 지정한
div위치에 플레이어가 렌더링되고, 방송이 ONAIR 상태면 영상 자동 재생을 시도한다 (브라우저 자동재생 정책에 따라 제한될 수 있음) - (방법 B) URL만으로 별도 페이지 구성 없이 플레이어가 열린다
- (방법 C) 지정한
div안에 대표 방송과 방송 목록이 그려지고, 목록의 방송을 누르면 모달이 열린다 - 공유 버튼을 켰다면, 클릭 시
shareUrl을 지정했으면 그 페이지로, 지정하지 않았으면 현재 페이지로 공유된다
이전 ← 화면 용어 가이드다음 → 미션 2 · 사용자 정보 연결
미션 2 · 사용자 정보 연결
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) | 설정한 사용자 정보를 지우고 게스트 상태로 돌아갑니다 |
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)
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단계에 더하는 작업이 아닙니다.
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 로 씁니다
setUserJWT())를 사용하세요. salt 정책은 Shoplive 팀과 합의된 값을 권장합니다.5단계 · JWT 인증용 토큰 만들기 (고객사 백엔드)
JWT 인증에 쓸 JWT 는 Shoplive 가 발급한 Secret Key 로 고객사 백엔드가 서명합니다. 페이지는 백엔드에서 받은 JWT 를 initPlugin({ userJWT }) 나 setUserJWT() 로 넘기기만 합니다(방법 B 는 tk 파라미터).
- Shoplive 담당자에게서 Secret Key 를 받습니다 — base64 로 인코딩된 문자열입니다
- 로그인한 사용자 정보로 아래 payload 를 만들고 HS256 으로 서명합니다
- 페이지가 요청할 때마다(
setUserJWT()를 부르기 직전마다) JWT 를 새로 발급해 내려 줍니다. 브라우저에 JWT 를 저장해 두고 다시 쓰지 않습니다
exp 까지 계속 쓸 수 있습니다. 페이지를 열 때마다, 또는 setUserJWT() 를 부를 때마다 백엔드에서 새 JWT 를 받게 하세요.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, 권장 64x64px | https://…/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' }
);
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 });
- 로그인한 시청자는
userId로 식별됩니다. 이 이벤트는 로그인 여부와 무관하게 오지만, 사용자 정보를 넘긴 시청자는 넘긴userId를 기준으로 하고uid는 게스트에게만 쓰세요. - 값이 바뀔 수 있습니다. 플레이어는 게스트
uid를 브라우저에 2시간 동안 보관하고, 그 뒤에 다시 들어오면 새 값을 발급합니다. 브라우저 저장소를 지우거나 다른 브라우저로 들어와도 달라집니다. 받을 때마다 저장해 둔 값과 비교해 갱신하세요.
완료 확인
- 로그인 후 채팅 전송 시 지정한 사용자 이름(회원 이름)이 표시된다
- (입장 제한을 걸었다면)
userScore/userType기준에 맞는 사용자만 정상적으로 입장된다 - 로그아웃 →
cloud.shoplive.logout()으로 사용자 정보를 초기화한 뒤 게스트로 시청된다
이전 ← 미션 1 · 방송 노출하기다음 → 미션 3 · 사용자 상호작용 처리하기
미션 3 · 사용자 상호작용 처리하기
initPlugin() 완료 · 쿠폰·상품 클릭을 다룬다면 Admin에서 해당 팝업·상품의 클릭 동작 설정 완료쿠폰·상품·로그인처럼 자체 비즈니스 로직이 필요한 상호작용은 대부분 messageCallback을 통해 들어옵니다. 이 단계의 내용이 실제 연동 코드의 핵심입니다.
messageCallback은 페이지에 로드된 SDK의 initPlugin()에 등록하는 기능입니다. shoplive.show 링크(미션 1 방법 B)는 페이지에 SDK 자체가 로드되지 않으므로 콜백을 등록할 수 없습니다. 쿠폰·상품 클릭에 반응하는 로직이 필요하면 방법 A를 사용하세요.initPlugin()호출 시messageCallback에 액션 이름별로 함수를 등록합니다.- 등록해두면 해당 액션이 발생할 때마다 자동으로 호출됩니다.
- (선택) 콜백 안에서
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
});
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_SCREENEXIT_FULL_SCREEN | 플레이어에서 전체 화면 진입 · 해제를 요청할 때 ① | campaignKey (해제는 없음) | 재정의 안 하면 플레이어 iframe 에 브라우저 전체 화면을 걸고 풉니다. 고객사가 자체 레이아웃으로 확대하려면 재정의 |
payload.url 에 파라미터가 하나 붙습니다
시청자가 브라우저 PIP 상태로 이동해 온 페이지에서는 CLICK_PRODUCT · LINK · LINK_NEW_WINDOW 의 payload.url 끝에 csl_bp 가 자동으로 붙습니다. 다음 페이지에서도 PIP 를 이어 가기 위한 표식입니다. 이 URL 을 고객사 로직에서 가공한다면 모르는 파라미터가 섞여 들어올 수 있다는 점을 감안하세요 — 지우면 이동한 페이지에서 PIP 가 끊깁니다.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_INITIALIZED | initPlugin() 이 완전히 끝났을 때. 초기화 전에 미리 부른 함수들이 모두 실행된 뒤에 옵니다 | 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_SHOWNON_PLAYER_MODAL_HIDDEN | 모달 플레이어가 화면에 뜨고 사라질 때 | campaign(Campaign) |
ON_LIVE_MINI_PREVIEW_SHOWNON_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_INON_CHAT_INPUT_FOCUS_OUT | 시청자가 채팅 입력창에 커서를 넣고 뺄 때. 모바일에서 키보드가 올라오는 시점입니다 — 아래 CHAT_READY 참고 | campaignKey |
ON_FILTERED_CHAT | chatFilterPatterns에 걸려 채팅이 전송되지 않았을 때 | 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 | 상품 설명 |
showingNow | NOW 마크 표시 여부 |
salesStatus | ON_SALE / SOLDOUT / ALMOST_SOLDOUT |
originalPrice | 판매가 |
discountedPrice | 할인가 |
discountPercentage | 할인율 |
currency | 통화 (예: KRW) |
isCartOn | 카트 담기 여부 |
장바구니 담기(CLICK_PRODUCT_CART) 액션도 같은 Goods 구조를 payload로 사용합니다.
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 | 이벤트 이름 |
|---|---|
click | back_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 |
show | player_on_foreground · player_on_background · player_active_seconds · product_list_banner · show_qna_chat_card · quiz_question_show · quiz_answer_show |
action | product_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 이 "방송을 어디에 그릴까"였다면, 이 미션은 그 페이지로 어떻게 오게 할까입니다. 세 가지 수단이 있고 서로 독립이니 필요한 것만 쓰세요.
미니 프리뷰 — 진행 중 방송을 다른 페이지 구석에 띄우기
진행 중인 방송이 있으면, 방송 페이지가 아닌 다른 페이지 구석에 작은 배너가 떠서 방송을 미리 보여 줍니다. 시청자가 누르면 모달 플레이어로 바로 이어집니다.
별도 호출은 필요 없습니다. 미리보기를 띄우고 싶은 페이지에 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 이상 | 17vw | 17vw × 15/9 (너비의 약 1.67배) |
| 481 ~ 1024px | 26vw | 26vw × 15/9 | |
| 480px 이하 | 35vw | 35vw × 6/4 (너비의 1.5배) | |
| 가로형 (15:9) | 모든 폭 | 26vw | 26vw × 9/15 |
| 방송 형태 | 최소 | 최대 |
|---|---|---|
| 세로형 | 100 × 150px | 200 × 332px |
| 가로형 | 160 × 96px | 332 × 200px |
비율이 9:16 · 16:9 가 아니라 9:15 · 15:9 입니다. 픽셀 단위로 자리를 맞출 때 주의하세요.
공유 URL — 링크 하나로 그 방송 열기
플레이어의 공유 버튼(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 });
}
_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 · 플레이어 제어하기 선택
미션 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 · 측정 — 유입과 구매
userId시청자가 상품을 눌러 상품 페이지로 이동하는 순간 SDK 의 추적은 끝납니다. 그 뒤 결제가 성사됐는지는 고객사 페이지에서만 알 수 있으므로, 구매 완료 시점에 한 번 알려 주어야 "이 방송이 매출을 얼마나 만들었는가"가 집계됩니다.
어디서 들어왔는지 — 마케팅 유입 추적
방송 페이지 주소에 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
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로 넘긴 값과 완전히 같은 값을 구매 완료 페이지에서도 넘깁니다. 해싱했다면 같은 해시값이어야 합니다 |
| 비로그인(게스트) | 브라우저에 저장된 익명 식별자 | 호출할 것은 없지만 같은 브라우저 · 같은 도메인이어야 합니다 |
| 자체 식별자가 있다면 | anonId | initPlugin({ 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 가 켜져 있으면 목록과 관계없이 전체에 노출됩니다.
useRemind | listCampaignIdsForRemindButton | 결과 |
|---|---|---|
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.product | productId · sku · name · brand · originalPrice · discountPrice · currency · url · imageUrl · stockStatus · auctionStatus |
payload.auction | winnerCheckoutScheme(낙찰자 결제 주소) · 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 로 전환합니다.
두 가지 방식
| 구분 | 웹 PIP | 브라우저 PIP |
|---|---|---|
| 어떻게 보이나 | 고객사 페이지 위에 작은 플레이어가 뜹니다. 시청자가 드래그로 옮길 수 있고 모양이 Shoplive 플레이어와 이어집니다 | 브라우저가 제공하는 기본 PIP 창입니다. 모양은 바꿀 수 없습니다 |
| 어디까지 따라가나 | 같은 탭 안에서만 유지됩니다 | 다른 탭·다른 사이트로 가도 따라갑니다 |
| 쓸 수 있는 조건 | 이동할 주소가 고객사와 같은 도메인이어야 합니다 (웹 PIP 을 쓰기 전에 확인할 세 가지) | 시청자가 직접 누른 동작이어야 합니다 (브라우저 PIP 의 제약) |
| 모바일에서 | 고객사 페이지 위에 직접 그리는 방식이라 데스크톱·모바일이 같습니다 | 약합니다 — Android 웹뷰에서는 제한적이고 인앱 브라우저에서는 동작하지 않습니다. 데스크톱 위주로 보세요 |
| 이런 경우에 | 자사몰 안에서 상품 상세로 보낼 때 · 모바일 비중이 높을 때 | 외부 도메인·다른 사이트로 보낼 때 · 데스크톱 비중이 높을 때 |
웹 PIP 을 쓰기 전에 확인할 세 가지
웹 PIP 은 이동할 페이지를 iframe 에 담아 화면 전체에 덮는 방식으로 동작합니다. 그래서 아래 중 하나라도 걸리면 켜도 화면이 그려지지 않습니다. 활성화를 요청하기 전에 먼저 확인하세요.
| 확인할 것 | 가능 | 불가 |
|---|---|---|
| 같은 도메인인가 | shop.com/live → shop.com/product/1 | shop.com/live → m.shop.com/product/1서브도메인만 달라도 안 됩니다 |
X-Frame-Options 헤더 | 자사 페이지에 SAMEORIGIN 으로 설정 | DENY 면 iframe 이 아예 그려지지 않습니다. 외부 사이트는 고객사가 바꿀 수 없습니다 |
Content-Security-Policy | frame-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
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단계(로그인 자동 연동)와 같고, 여기서는 위치와 플랫폼별 회원 변수만 다룹니다.
- 운영 중인 스킨을 복사해 편집용 스킨에서 작업합니다 — 저장 즉시 실제 쇼핑몰에 반영되는 것을 막습니다
- 공통 레이아웃(모든 페이지가 함께 쓰는 상단 영역)에 SDK 로드 · 회원 정보 엘리먼트 ·
initPlugin()을 넣습니다 — 아래 코드 ① - 방송 목록을 보여 줄 페이지를 새로 만들고
setOverall()을 넣습니다 — 아래 코드 ② - 메뉴(진입점)에 그 페이지 주소를 연결합니다
- 진행 중인 방송이 있을 때 미니 프리뷰가 뜨는지 확인합니다. 안 뜨면 Admin 방송 상세에서 API · 플러그인 공개를 켭니다
① 공통 레이아웃
<!-- 공통 레이아웃 — 모든 페이지에 한 번만 (위치는 플랫폼별 안내 참고) -->
<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)으로 연결 — 권장
- Shopify 앱스토어의 ShopliveAssistant 페이지에서 오른쪽 위 계정 아이콘으로 연동할 스토어가 맞는지 확인합니다
- Install → 앱이 요청하는 권한(담당자 정보 · 상품 데이터 등)을 확인하고 설치를 마칩니다
- Shopify 관리자 Apps › Shoplive Assistant 에서 Shoplive 계정으로 로그인합니다
- 스토어에 연결된 Access Key 를 확인하고(편집 가능), 상품 동기화로 Shopify 상품을 Shoplive 로 가져옵니다 — 최근 변경분만 / 옵션을 개별 상품으로 중 선택
- 앱은 계정 연결과 상품 동기화까지입니다. 플레이어는 아래 테마 코드로 설치합니다
| 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 · 명령 사전 |
| PIP | navigateWithPIP(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.viewerCount | true | 시청자 수 표시 |
ui.likeCount | true | 좋아요 수 표시 |
ui.backButton | false | 뒤로가기 버튼 표시 |
ui.shareButton | false | 공유 버튼 표시 |
isFullScreen | false | 항상 전체 화면으로 실행 (viewport meta 필요) |
hideControls | false | 컨트롤 UI를 숨긴 채 시작 |
playerAlias | — | getPlayer('별명')으로 집어낼 별명 (미션 5) |
closePlayerAfterCampaignEnd | — | 방송이 끝나고 이 시간(ms)이 지나면 CLOSE_PLAYER 이벤트가 옵니다. SDK 는 이 이벤트로 아무것도 하지 않으므로 플레이어를 실제로 치우려면 고객사가 destroyPlayer() 또는 dismissLayerModalPlayer() 를 직접 호출해야 합니다.지정하지 않으면 방송 종료 30초 뒤 플레이어가 스스로 새로고침됩니다(종료 화면으로 정리) |
chatFilterPatterns | — | 정규식 문자열 배열. 일치하는 채팅은 전송되지 않고 ON_FILTERED_CHAT 발생 |
passcode | — | 리허설 방송 입장 코드 |
brand | — | 브랜드 좋아요 초기 상태 — { identifier, favorite } (부록 A) |
라이브 페이지 템플릿 — setOverall(containerId, config)
| 필드 | 기본값 | 설명 |
|---|---|---|
featuredOnly | false | 대표 방송(플레이어) 영역만 노출 |
featured.fixedCampaignKey | — | 대표 방송을 특정 방송으로 고정 |
campaignListsOnly | false | 방송 목록 영역만 노출 |
campaignStatus | 전체 | 'READY' · 'ONAIR' · 'CLOSED'. 문자열 또는 배열(적은 순서대로 정렬) |
tags / tagSearchOperator | — | 태그 필터 · 'OR' | 'AND' |
displayTags | — | 목록 위에 필터 탭으로 노출할 태그 |
skus / goodsIds | — | 특정 상품이 포함된 방송만 — 고객사 상품코드 / Shoplive 내부 상품 ID |
campaignList.layout | 'GRID' | 'GRID' · 'HORIZONTAL' |
campaignList.type | — | 'SECTION' — 상태별 섹션으로 나눠 표시 |
hasMoreButton | true | false면 'GRID'에서 무한 스크롤 |
ascending | 상태에 따라 | 정렬 방향. 미지정 시 campaignStatus가 'READY' 문자열 하나일 때만 오름차순 |
sectionTitles | 기본 문구 | { onair, ready, closed, all } |
disableHistoryHook | false | 목록에서 모달을 열 때 주소를 건드리지 않음 |
그 밖의 함수
| 함수 | 설정 객체 |
|---|---|
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 |
canBid | true / false. 경매 입찰 자격 (부록 A · 옥션). 플랫폼 자동 연동은 이 값을 항상 true 로 넣습니다 |
이벤트 사전
initPlugin({ messageCallback })에 등록할 수 있는 이벤트 전체입니다. 쓰는 법과 예제는 미션 3에 있고, 여기서는 이름으로 빠르게 찾는 용도입니다. 화면 열의 번호는 화면 용어 가이드의 그림과 대응합니다.
기본 동작이 있는 이벤트 — 등록하면 대체됩니다
등록하지 않아도 SDK가 알아서 처리합니다. 등록하는 순간 기본 동작은 더 이상 실행되지 않으므로, 로깅만 하려던 경우에도 원래 동작을 직접 다시 구현해야 합니다.
| 이벤트 | 기본 동작 | 화면 |
|---|---|---|
CLICK_PRODUCT | 새 창으로 payload.url 이동 | ⑥ |
LINK | 현재 창에서 payload.url 이동 (웹 PIP 켜짐 시 플레이어 유지) | ⑨ ⑩ ⑪ |
LINK_NEW_WINDOW | 새 창으로 payload.url 이동 | ⑨ ⑩ ⑪ |
CLICK_SHARE_BTN | OS 공유 시트(navigator.share) → 없으면 클립보드 복사 (공유 URL) | ⑫ |
CLICK_BACK_BUTTON | history.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_REQUIREDON_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 하위 명령
활성화가 필요한 기능에서 상태를 되돌려 줄 때 씁니다.
| command | payload | 쓰는 곳 |
|---|---|---|
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 CampaignKey | setPlayer 에 넘긴 campaignKey가 비어 있거나 누락됨 |
Invalid AccessKey | setOverall · setLivePreview 등에서 accessKey를 찾지 못함. initPlugin 에서 받은 값도, 함수에 직접 넘긴 값도 없는 경우입니다 |
Empty AccessKey | initPlugin 에 accessKey가 비어 있음 |
Invalid WebClientSettings | accessKey가 잘못됐거나 초기 설정 조회 실패 — 아래 주의 참고 |
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-src | https://static.shoplive.cloudhttps://cdnjs.cloudflare.com | SDK 본체 · hls.js — SDK 가 영상 재생용 hls.js 를 고객사 페이지에 직접 붙이므로 Shoplive 도메인이 아닌 이 출처도 필요합니다. 빠뜨리면 SDK 는 뜨는데 영상만 재생되지 않습니다 |
style-src | https://static.shoplive.cloud · 'unsafe-inline' | SDK 스타일시트 · 계정 커스텀 스타일(인라인) |
frame-src | https://static.shoplive.cloud | 플레이어 iframe — 빠뜨리면 플레이어 자리가 빈 채로 남습니다 |
connect-src | https://config.shoplive.cloud · https://capi.shoplive.cloud · https://api.shoplive.cloud · https://*.shoplive.cloud | 고객사 설정 · 방송 목록 API · 전환 이벤트 · 이벤트 로그 |
img-src | https://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에 조건이 정리돼 있으니 먼저 어느 쪽인지 확인하세요.
- 웹 PIP — 옮겨 갈 페이지가 세 가지 조건(같은 출처 ·
X-Frame-Options·frame-ancestors)을 하나라도 어기면 화면이 비거나 PIP 가 사라집니다. 결제 · 로그인 페이지에서는 의도적으로 빠져나오도록 되어 있습니다. - 브라우저 PIP — 시청자가 직접 누르는 동작 없이는 시작되지 않고, Android 웹뷰에서는 제한적입니다.
둘 다 계정에서 쓸 수 있도록 설정돼 있어야 하니, 조건을 다 맞췄는데도 안 되면 Shoplive 담당자에게 문의하세요.
구매 전환이 잡히지 않아요
sendConversionEvent() 도 초기화가 끝나기 전에 부르면 대기열에 쌓였다가 초기화 완료 시점에 실행됩니다. 그래서 초기화가 실패하면 전환 이벤트도 함께 사라집니다 — 콘솔에 Invalid WebClientSettings 가 있는지부터 확인하세요 (위 플레이어가 뜨지 않아요 참고).
결제 완료 페이지처럼 플레이어가 없는 페이지에서도 initPlugin() 을 먼저 불러야 전환이 전송됩니다. 보내는 위치와 필드는 미션 6, 시청 기록과 이어 붙는 기준은 3단계에 있습니다.
TypeScript 프로젝트인데 타입 정의가 있나요?
공식적으로 제공되는 타입 정의(.d.ts)는 없습니다. window.cloud.shoplive를 any로 선언하거나, 이 문서에 나온 필드 기준으로 필요한 만큼만 직접 타입을 선언해서 사용하세요.