웹 SDK - 네이티브 자바Script 구현 가이드

문서

개요

참고: 웹 어트리뷰션은 엔터프라이즈 기능입니다. 계정에 이 기능을 활성화하려면 고객 성공 매니저에게 문의하십시오.

이 가이드는 네이티브 JavaScript를 사용하여 Singular WebSDK를 구현하는 방법을 안내합니다. 이 방법은 가장 안정적인 추적을 제공하며, 일반적인 광고 차단기에 의해 차단되지 않으므로 대부분의 구현에 권장됩니다.

중요!

  • 네이티브 자바Script와 Google 태그 관리자 방식을 동시에 구현하지 마십시오. 중복 추적을 방지하기 위해 하나만 선택하십시오.
  • Singular 웹 SDK는 사용자의 브라우저에서 클라이언트 측으로 실행되도록 설계되었습니다. 정상적인 작동을 위해서는 localStorageDOM(Document Object Model) 과 같은 브라우저 기능에 대한 접근이 필요합니다. 서버 측 (예: Next.js SSR 또는 node.js를 통한)에서 SDK를 실행하려고 시도하지 마십시오. 서버 환경은 브라우저 API에 대한 접근을 제공하지 않으므로 추적 오류가 발생합니다.

필수 조건

시작하기 전에 다음을 준비하십시오:

  • SDK 키 및 SDK 시크릿: 
  • 제품 ID:
    • 정의: 웹사이트의 고유 이름으로, 역방향 DNS 형식(예: com.website-name)을 사용하는 것이 이상적입니다.
    • 중요성: 이 ID 웹사이트를 Singular 내 앱으로 연결하며, Singular의 앱 페이지에 기재된 웹 앱 bundleID와 반드시 일치해야 합니다. 
  • 웹사이트 HTML 코드 편집 권한.
  • 페이지의 <head> 섹션에 자바Script 추가 권한.
  • 추적하려는 이벤트 목록. 아이디어를 얻으려면 Singular 표준 이벤트: 전체 목록 및 업종별 추천 이벤트를 참조하세요.

구현 단계


1단계: SDK 라이브러리 Script 추가

웹사이트의 모든 페이지 <head> 섹션에 다음 코드 스니펫을 추가하세요. 가능한 한 상단에 배치하며, 이상적으로는 <head> 태그 근처에 위치시켜야 합니다.

팁! Script를 조기에 추가하면 페이지 소스 코드의 모든 Singular 기능에 Singular 자바Script 라이브러리가 사용 가능해집니다.

Latest VersionSpecific VersionUsing NPMNext.js / React
<script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>

2단계: SDK 초기화

  • 브라우저에서 페이지가 로드될 때마다 항상 SDK를 초기화하십시오.
  • 모든 Singular 어트리뷰션 및 이벤트 추적 기능에는 초기화가 필요합니다.
  • 초기화는 __PAGE_VISIT__ 이벤트를 트리거합니다.
  • __PAGE_VISIT__ 이벤트는 다음 조건이 충족될 때 서버 측에서 새 세션을 생성하는 데 사용됩니다:
    • 사용자가 URL에 새로운 광고 데이터(예: UTM 또는 WP 매개변수)를 가지고 접속하거나,
    • 이전 세션이 만료된 경우(30분 동안 활동이 없을 때).
    • 세션은 사용자 유지율 측정 및 리인게이지먼트 기여도 분석을 지원하기 위해 사용됩니다.
  1. 초기화 함수를 생성하고 페이지 로드 후 DOM Ready 시점에 이 함수를 호출하십시오.
  2. 다른 Singular 이벤트가 보고되기 전에 초기화가 이루어지도록 하십시오.
  3. Singular 페이지 애플리케이션(SPA)의 경우, 첫 페이지 로드 시 Singular SDK를 초기화한 다음, 새로운 페이지 뷰를 나타내는 모든 경로 변경 시 Singular 페이지 방문 함수 window.singularSdk.pageVisit() 를 호출하십시오.
Basic InitializationNext.js / React

기본 DOM 준비 완료 초기화

DOMContentLoaded 에서 initSingularSDK()호출을 위한 이벤트 리스너 추가

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK with default config
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});

글로벌 속성을 사용한 기본 초기화

Singular SDK가 초기화되지 않았으므로, 브라우저의 localstorage 에서 글로벌 속성을 설정하는 사용자 정의 함수를 구현해야 합니다. 사용자 정의 함수 setGlobalPropertyBeforeInit() 를 참조하십시오. 구현 후에는 SDK 초기화 전에 아래와 같이 속성을 설정할 수 있습니다.

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK with default config
 */
function initSingularSDK() {
  var sdkKey = 'sdkKey';
  var sdkSecret = 'sdkSecret';
  var productId = 'productId';
  
  // Set global properties before SDK initialization
  setGlobalPropertyBeforeInit(sdkKey, productId, 'global_prop_1', 'test', false);
  setGlobalPropertyBeforeInit(sdkKey, productId, 'global_prop_2', 'US', true);
  
  // Initialize SDK
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});

SPA(Single-Page Application) 라우팅을 통한 기본 초기화

시나리오 처리 방법

첫 페이지 로드

window.singularSdk.init(config) 호출

새로운 경로/페이지로 이동

window.singularSdk.pageVisit() 호출

SPA 초기 로드 시

window.singularSdk.pageVisit() 호출하지 않음 (초기화가 첫 페이지 방문 이벤트를 제공함)

SPA(React) 예시

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

/**
 * Initializes the Singular SDK with the provided configuration.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * // Initialize the Singular SDK
 * initSingularSDK();
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId');
  window.singularSdk.init(config);
}

/**
 * Tracks a page visit event with the Singular SDK on route changes.
 * @example
 * // Track a page visit
 * trackPageVisit();
 */
function trackPageVisit() {
  window.singularSdk.pageVisit();
}

/**
 * A React component that initializes the Singular SDK on mount and tracks page visits on route changes.
 * @returns {JSX.Element} The component rendering the SPA content.
 * @example
 * // Use in a React SPA with react-router-dom
 * function App() {
 *   const location = useLocation();
 *   useEffect(() => {
 *     initSingularSDK();
 *   }, []);
 *   useEffect(() => {
 *     trackPageVisit();
 *   }, [location.pathname]);
 *   return <div>Your SPA Content</div>;
 * }
 */
function App() {
  const location = useLocation();

  useEffect(() => {
    initSingularSDK();
  }, []); // Run once on mount for SDK initialization

  useEffect(() => {
    trackPageVisit();
  }, [location.pathname]); // Run on route changes for page visits

  return (
    <div>Your SPA Content</div>
  );
}

export default App;

초기화 콜백을 사용한 기본 초기화

SDK 준비 완료 후 코드를 실행해야 하는 경우(예: Singular 기기 ID 획득), .withInitFinishedCallback() 로 콜백 설정:

/**
 * Initializes the Singular SDK with a callback to handle initialization completion.
 * @param {string} sdkKey - The SDK key for Singular.
 * @param {string} sdkSecret - The SDK secret for Singular.
 * @param {string} productId - The product ID for Singular.
 * @example
 * initSingularSDK(); // Initializes SDK and logs device ID
 */
function initSingularSDK() {
  var config = new SingularConfig('sdkKey', 'sdkSecret', 'productId')
    .withInitFinishedCallback(function(initParams) {
      var singularDeviceId = initParams.singularDeviceId;
      // Example: Store device ID for analytics
      console.log('Singular Device ID:', singularDeviceId);
      // Optionally store in localStorage or use for event tracking
      // localStorage.setItem('singularDeviceId', singularDeviceId);
    });

  window.singularSdk.init(config);
}

/**
 * Triggers Singular SDK initialization when the DOM is fully parsed.
 */
document.addEventListener('DOMContentLoaded', function() {
  initSingularSDK();
});
  • 실제 SDK 키로 'sdkKey' 실제 SDK 키로 교체하십시오.
  • 실제 SDK 시크릿으로 'sdkSecret' 실제 SDK 비밀 키로 교체하십시오.
  • Replace 'productId' 실제 제품 ID로 대체하십시오. 형식은 다음과 같아야 합니다: com.website-name 그리고 Singular 플랫폼의 앱 페이지에 있는 번들 ID 값과 일치해야 합니다.

구성 옵션

.with 메서드를 연결하여 추가 기능을 활성화함으로써 WebSDK 설정을 강화하십시오.

예를 들어, 쿠키에 Singular Device ID(SDID)를 저장하여 크로스 서브도메인 추적을 지원하거나, 활성 로그인 상태의 재방문 사이트 방문자를 위한 사용자 지정 ID를 포함하려면:

Config with Options
var domain = 'website-name.com';
var config = new SingularConfig('sdkKey','sdkSecret','productId')
  .withAutoPersistentSingularDeviceId(domain)
  .withCustomUserId(userId);

SingularConfig 메서드 참조

사용 가능한 모든 ".with" 메서드는 다음과 같습니다.

메소드 설명 자세히 보기
.withCustomUserId(customId) 사용자 ID를 Singular로 전송 사용자 ID 설정
.withProductName(productName) 제품에 대한 선택적 표시 이름  
.withLogLevel(logLevel) 로깅 수준 구성: 0 - 없음(기본값); 1 - 중요; 2 - 정보; 3 - 디버그.  
.withSessionTimeoutInMinutes(timeout) 세션 시간 초과를 분 단위로 설정 (기본값: 30분)

 

.withAutoPersistentSingularDeviceId(domain) 자동 크로스 서브도메인 추적 활성화 쿠키를 사용한 자동 지속

.withPersistentSingularDeviceId(singularDeviceId)

수동 크로스-서브도메인 추적 활성화 Singular 기기 ID 수동 설정
.withInitFinishedCallback(callback) SDK 초기화 완료 시 콜백 호출 초기화 완료 시 콜백 함수 호출
.withGlobalProperties(globalProperties, false) 초기화 중 글로벌 속성 설정; 두 번째 매개변수는 overrideExisting (부울)입니다. false 로 기존 속성과 병합하고, true 로 기존 속성을 덮어씁니다. 글로벌 속성
.withEventsDedupEnabled() 선택적 이벤트 중복 제거를 활성화하여 중복 이벤트 내보내기를 줄입니다 (예: 짧은 시간 내에 반복적으로 발동된 트리거) 이벤트 중복제거
.withTimeBetweenEvents(timeBetweenEvents) 중복 제거를 위한 최대 시간 창(ms) 설정(기본값: 1000ms / 1초) 이벤트 중복제거

개발자 체크리스트

  • SDK 자격 증명과 제품 ID를 수집하십시오.
  • 사용자 ID, 타임아웃 등 사용자 정의 설정이 필요한지 결정하십시오.
  • 위의 예시를 사용하여 Singular 초기화 함수와 SingularConfig 객체를 구성하십시오. 항상 초기화가 페이지 로드 시 단 한 번만 트리거되는지 테스트하십시오.
  • 항상 테스트하여 초기화가 페이지 로드 시 단 한 번만 트리거되는지 확인하십시오.

팁! 자연검색 추적과 같은 고급 설정의 경우, Singular SDK 초기화 전에 쿼리 매개변수 조정 등 사용자정의 JavaScript를 구현해야 할 수 있습니다. 변경 사항이 정확히 캡처되도록 사용자 정의 코드가 Singular 초기화 함수보다 먼저 실행되도록 하세요. 자연 검색 추적 구현 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다.


3단계: 이벤트 추적

SDK 초기화 후, 사용자가 웹사이트에서 중요한 행동을 수행할 때 사용자 정의 이벤트를 추적할 수 있습니다.

중요! Singular는 중복 이벤트를 차단하지 않습니다! 페이지 새로고침이나 중복 발생을 방지하는 보호 장치는 개발자의 책임입니다. 구매 데이터 오류 방지를 위해 구매 이벤트 전용 중복 제거 방법을 적용하는 것이 권장됩니다. 예시는 아래 "단계 5: 중복 이벤트 방지"를 참조하십시오.

Basic EventConversion EventRevenue Event

기본 이벤트 추적

단순한 이벤트를 추적하거나 유효한 JSON을 사용하여 사용자 정의 속성을 추가해 이벤트에 대한 추가 컨텍스트를 제공하세요:

// Basic event tracking
var eventName = 'page_view';
window.singularSdk.event(eventName);
      
// Optional: With attributes for more context
var eventName = 'sng_content_view';
var attributes = {
  key1: 'value1', // First custom attribute
  key2: 'value2'  // Second custom attribute
};
window.singularSdk.event(eventName, attributes);

일반적인 이벤트 구현 패턴

Page Load EventsButton Click EventsForm Submission Events

페이지 로드 이벤트

페이지 로드 이벤트 추적 메커니즘은 JavaScript를 사용하여 웹페이지가 완전히 로드된 시점을 모니터링한 후 Singular SDK를 통해 분석 이벤트를 트리거합니다. 구체적으로, window.addEventListener('load', ...) 메서드를 활용하여 모든 페이지 리소스(예: HTML, 이미지, Script)가 로드된 시점을 감지합니다. 이 이벤트 발생 시 Singular SDK의 ` event ` 함수가 호출되어 관련 속성을 가진 커스텀 이벤트를 기록합니다. 이를 통해 페이지 뷰 모니터링이나 등록과 같은 사용자 행동 추적 등 분석 목적의 사용자 상호작용을 추적할 수 있습니다. 이 메커니즘은 웹 분석에서 페이지 방문이나 특정 행동과 같은 사용자 행동 데이터를 포착하고, 상세한 인사이트를 위해 사용자 정의 가능한 속성을 활용하는 데 일반적으로 사용됩니다.

이 메커니즘은 아래와 같이 사용자 정의 속성을 가진 특정 이벤트(예: page_view )를 추적하도록 적용할 수 있습니다. 코드 구조는 명확성과 유지보수성을 위해 이벤트 이름과 속성을 별도로 정의하여 깔끔하고 모듈화됩니다.

/**
 * Tracks a registration event with the Singular SDK when the page fully loads.
 * @param {string} eventName - The name of the event to track (e.g., 'page_view').
 * @param {Object} attributes - A JSON object with custom event attributes.
 * @param {string} attributes.key1 - First custom attribute (e.g., 'value1').
 * @param {string} attributes.key2 - Second custom attribute (e.g., 'value2').
 */
window.addEventListener('load', function() {
  var eventName = 'page_view';
  var attributes = {
    key1: 'value1', // First custom attribute
    key2: 'value2'  // Second custom attribute
  };
  window.singularSdk.event(eventName, attributes);
});

4단계: 고객 사용자 ID 설정

Singular SDK 메서드를 사용하여 내부 사용자 ID를 Singular로 전송할 수 있습니다.

참고: Singular의 크로스 디바이스 솔루션을 사용하는 경우 모든 플랫폼에서 사용자 ID를 수집해야 합니다.

  • 사용자 ID는 어떤 식별자든 가능하며 PII(개인 식별 정보)를 노출해서는 안 됩니다. 예를 들어, 사용자의 이메일 주소, 사용자 이름 또는 전화번호를 사용해서는 안 됩니다. Singular는 자사 데이터에만 고유한 해시 값을 사용할 것을 권장합니다.
  • Singular로 전달되는 사용자 ID 값은 모든 플랫폼(웹/모바일/PC/콘솔/오프라인)에서 수집하는 내부 사용자 ID와 동일해야 합니다.
  • Singular는 사용자 수준 내보내기, ETL 및 내부 BI 포스트백(구성된 경우)에 사용자 ID를 포함합니다. 사용자 ID는 퍼스트 파티 데이터이며 Singular는 이를 타사와 공유하지 않습니다.
  • Singular SDK 메서드로 설정된 사용자 ID 값은 logout() 메서드로 해제될 때까지 또는 브라우저 로컬 스토리지가 삭제될 때까지 유지됩니다. 웹사이트를 닫거나 새로 고침해도 사용자 ID는 해제되지 않습니다.
  • 비공개/시크릿 모드에서는 브라우저가 종료 시 로컬 스토리지를 자동 삭제하므로 SDK가 사용자 ID를 유지할 수 없습니다.

사용자 ID를 설정하려면 ` login() ` 메서드를 사용하십시오. 사용자 ID를 해제하려면(예: 사용자가 계정에서 "로그아웃"하는 경우) ` logout() ` 메서드를 호출하십시오.

참고: 여러 사용자가 Singular 기기를 사용하는 경우, 로그인 및 로그아웃 시마다 사용자 ID를 설정해제하는 로그아웃 절차를 구현하는 것이 좋습니다.

웹사이트에서 Singular SDK를 초기화할 때 사용자 ID를 이미 알고 있다면, config 객체에 사용자 ID를 설정하십시오. 이렇게 하면 Singular가 첫 세션부터 사용자 ID를 가질 수 있습니다. 그러나 일반적으로 사용자 ID는 사용자가 등록하거나 로그인을 수행합니다. 이 경우 등록 절차가 완료된 후 login() 를 호출하십시오.

팁! 모바일 SDK에서 사용하는 것과 동일한 고객 사용자 ID를 사용하세요. 이를 통해 크로스 디바이스 어트리뷰션이 가능해지며 플랫폼 전반에 걸친 사용자 행동에 대한 완전한 시각을 제공합니다.

고객 사용자 ID 모범 사례:

  • 사용자가 로그인하거나 가입하는 즉시 설정하세요
  • 웹 및 모바일 플랫폼 전반에서 동일한 ID 사용
  • 개인 식별 정보(이메일, 전화번호)를 사용하지 마십시오
  • 내부 사용자 ID 또는 데이터베이스 ID를 사용하세요
  • 숫자라도 문자열로 전송하십시오:
Set the User IDUnset the User ID
// After user logs in or signs up
var userId = 'user_12345';
window.singularSdk.login(userId);

5단계: 이벤트 중복 제거(선택 사항)

중요! 사이트에서 동일한 트리거가 짧은 시간 내에 여러 번 발동될 경우 중복 이벤트가 내보내질 수 있습니다. SDK 중복 제거 기능을 활성화하면 중복 이벤트가 자동으로 억제됩니다.

Singular의 웹 SDK는 짧은 시간 내에 반복적으로 발동되는 트리거로 인한 중복 내보내기를 줄이기 위해 선택적 이벤트 중복 제거를 지원합니다. 활성화 시, SDK는 설정된 시간 창 내에서 동일한 중복 제거 매개변수와 일치하는 중복 이벤트를 제거합니다.

활성화 방법

  • withEventsDedupEnabled: 이벤트 중복 제거 활성화 (기본값은 비활성화됨).
  • withTimeBetweenEvents: 두 이벤트를 중복으로 간주하는 최대 시간 창(밀리초 단위); 기본값은 1000ms(1초)입니다.
JavaScript

JavaScript

// Enable optional event deduplication (recommended when triggers may fire repeatedly)
var config = new SingularConfig("SDK_KEY", "SDK_SECRET", "PRODUCT_ID")
  .withEventsDedupEnabled()     // turns deduplication on
  .withTimeBetweenEvents(1000); // dedup window in ms (default: 1000 = 1s)

window.singularSdk.init(config);

중복 감지 방식

중복 제거가 활성화되면 SDK는 이벤트의 주요 필드에서 해시를 계산하고 시간 창 내 중복을 억제합니다. 중복 제거 매개변수에는 다음이 포함됩니다: EventName, EventProductName, IsRevenueEvent, CustomUserId, GlobalProperties, MatchId, WebUrl (SDK는 해싱 시 구매/인수(args)와 같은 추가 이벤트 "추가" 페이로드도 고려합니다).


6단계: 구현 테스트

SDK 구현 후 브라우저 개발자 도구를 사용하여 정상 작동 여부를 확인하십시오.

SDK 로드 확인

  1. 브라우저에서 웹사이트 열기
  2. 개발자 도구 열기(F12 또는 마우스 오른쪽 버튼 클릭 → 검사)
  3. 콘솔 탭으로 이동
  4. typeof singularSdk 를 입력하고 Enter 키를 누르세요
  5. "undefined"가 아닌 "function" 객체가 표시되어야 합니다

네트워크 요청 확인

  1. 개발자 도구 열기 (F12)
  2. 네트워크 탭으로 이동하세요
  3. 페이지를 다시 로드하세요
  4. singular 또는 sdk-api로 필터링하세요
  5. sdk-api-v1.singular.net로 전송된 요청 찾기
  6. 요청을 클릭하여 세부 정보 보기
  7. 상태 코드가 200인지 확인하세요
  8. 요청 페이로드에 제품 ID가 포함되어 있는지 확인하세요. 이는 페이로드의 "i" 매개변수에 포함됩니다.

성공! 상태 코드 200으로 sdk-api-v1.singular.net 에 대한 요청이 보인다면, SDK가 Singular로 데이터를 성공적으로 전송하고 있는 것입니다.

이벤트 확인

  1. 웹사이트에서 이벤트를 트리거하세요(버튼 클릭, 양식 제출 등).
  2. 네트워크 탭에서 sdk-api-v1.singular.net로의 새 요청을 찾으세요.
  3. 요청을 클릭하고 페이로드 또는 요청 탭을 확인하세요.
  4. 요청에 이벤트 이름 "n" 매개변수가 표시되는지 확인하세요

    event_test.png

종단 간 테스트를 위해서는 테스트 콘솔을 사용하십시오

Singular 대시보드에서 제공되는 SDK 콘솔을 사용하여 웹 SDK 연동을 테스트할 수 있습니다.

  1. Singular 플랫폼에서 개발자 도구 > 테스트 콘솔로 이동하십시오.
  2. 장치 추가를 클릭하십시오.
  3. 플랫폼 ""을 선택하고 Singular 기기 ID를 입력하세요.
  4. 브라우저 페이로드에서 Singular 장치 ID를 확인하세요. 이벤트에서 SDID 를 찾는 방법은 위 스크린샷을 참조하세요.
  5. 테스트 콘솔은 별도의 창에서 테스트하는 동안 활성 상태를 유지해야 합니다. 콘솔이 닫히거나 오프라인 상태일 때 발생하는 이벤트는 표시되지 않습니다.
  6. SDID를 추가한 후 웹페이지를 재로드하고 이벤트를 트리거하세요. "__PAGE_VISIT__" 및 기타 이벤트(트리거된 경우)가 SDK 콘솔에 표시됩니다.
  7. 테스트 결과를 확인하는 또 다른 방법은 로그 내보내기 (보고서 및 인사이트 로그 내보내기)를 활용하는 것입니다. 이 데이터 세트는 1시간 지연됩니다.

7단계: 웹-앱 전달 구현

웹-앱 어트리뷰션 전달

Singular WebSDK를 사용하여 웹사이트에서 모바일 앱으로의 사용자 여정을 추적하여 웹 캠페인에 대한 모바일 앱 설치 및 리인게이지먼트에 대한 정확한 어트리뷰션을 가능하게 합니다. 데스크톱 사용자를 위한 QR 코드 지원을 포함하여 웹-앱 전달을 설정하려면 다음 단계를 따르십시오.

  1. 모바일 웹 어트리뷰션용 Singular WebSDK 구성은 웹사이트-모바일 앱 어트리뷰션 전달 가이드를 참조하십시오.
  2. 데스크톱 웹-앱 추적의 경우:
    • 상기 가이드의 설정을 완료하세요.
    • Singular 모바일 웹-앱 링크(예: https://yourlink.sng.link/...)와 QRCode.js 같은 동적 QR 코드 라이브러리를 사용한 WebSDK 함수 buildWebToAppLink(link) 를 활용하세요.
    • 웹페이지에 링크를 인코딩한 QR 코드를 생성하세요. 데스크톱 사용자는 모바일 기기로 이를 스캔하여 앱을 열고, 어트리뷰션용 캠페인 데이터를 전달할 수 있습니다.
    • QR 코드 생성 및 웹-투-앱 링크 처리의 작동 예시는 Singular 웹SDK 데모에서 확인하세요.

팁! 모바일 인앱 브라우저 웹 뷰(Facebook, Instagram, TikTok 등에서 사용)는 사용자가 기기의 기본 브라우저로 이동할 경우 Singular 기기 ID가 변경되어 어트리뷰션에 차질을 빚을 수 있습니다.

이를 방지하려면 각 광고 네트워크에 맞는 Singular 추적 링크 형식을 반드시 사용하세요:


Singular WebSDK 데모

아래는 문서화된 Singular WebSDK API를 사용한 간단하지만 포괄적인 구현 예시입니다. 이 샘플은 WebSDK 지원 웹-투-앱 전달 기능을 활용하여 커스텀 이벤트, 전환 이벤트, 구매 이벤트웹-투-앱 링크 지원을 명확하게 구분합니다.

이 코드는 로컬 HTML 파일에서 실행하거나 사이트에 수정하여 고급 연동 및 문제 해결을 수행할 수 있습니다.

Singular WebSDK 데모 (소스 코드)
#

아래 코드를 HTML 파일로 복사하여 붙여넣고 브라우저에서 열어보세요.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <title>Singular WebSDK Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
  <script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>

  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto, sans-serif;
      line-height: 1.6;
      color: #1a1d29;
      background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
      padding: 40px 20px;
      min-height: 100vh;
    }

    .container {
      max-width: 800px;
      margin: 0 auto;
      background: white;
      padding: 48px;
      border-radius: 16px;
      box-shadow: 0 4px 24px rgba(30, 41, 59, 0.08);
      border: 1px solid rgba(148, 163, 184, 0.1);
    }

    .header-brand {
      display: flex;
      align-items: center;
      margin-bottom: 32px;
      padding-bottom: 24px;
      border-bottom: 2px solid #f1f5f9;
    }

    .brand-dot {
      width: 12px;
      height: 12px;
      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
      border-radius: 50%;
      margin-right: 12px;
    }

    h1 {
      font-size: 32px;
      font-weight: 700;
      color: #0f172a;
      letter-spacing: -0.025em;
    }

    h2 {
      font-size: 24px;
      font-weight: 600;
      margin: 48px 0 24px 0;
      color: #1e293b;
      padding-bottom: 12px;
      border-bottom: 2px solid #e2e8f0;
      position: relative;
    }

    h2:before {
      content: '';
      position: absolute;
      bottom: -2px;
      left: 0;
      width: 60px;
      height: 2px;
      background: linear-gradient(90deg, #6366f1, #8b5cf6);
    }

    p {
      margin-bottom: 18px;
      color: #475569;
      font-size: 15px;
    }

    strong {
      color: #1e293b;
      font-weight: 600;
    }

    .form-group {
      margin-bottom: 24px;
    }

    .radio-group {
      display: flex;
      gap: 24px;
      margin-bottom: 24px;
      padding: 16px;
      background: #f8fafc;
      border-radius: 12px;
      border: 1px solid #e2e8f0;
    }

    .radio-group label {
      display: flex;
      align-items: center;
      gap: 10px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      color: #475569;
      transition: color 0.2s ease;
    }

    .radio-group label:hover {
      color: #1e293b;
    }

    input[type="radio"] {
      width: 20px;
      height: 20px;
      cursor: pointer;
      accent-color: #6366f1;
    }

    input[type="text"] {
      width: 100%;
      padding: 14px 18px;
      font-size: 15px;
      font-weight: 400;
      border: 2px solid #e2e8f0;
      border-radius: 12px;
      transition: all 0.2s ease;
      background: #fafbfc;
      color: #1e293b;
    }

    input[type="text"]:focus {
      outline: none;
      border-color: #6366f1;
      background: white;
      box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
    }

    input[type="text"]::placeholder {
      color: #94a3b8;
      font-weight: 400;
    }

    button {
      padding: 14px 32px;
      font-size: 15px;
      font-weight: 700;
      color: #fff;
      background: linear-gradient(90deg, #2363f6 0%, #1672fe 100%);
      border: none;
      border-radius: 4px;  /* Pill shape */
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(35, 99, 246, 0.12);
      transition: background 0.2s, transform 0.2s;
      letter-spacing: 0.03em;
    }
    button:hover {
      background: linear-gradient(90deg, #1672fe 0%, #2363f6 100%);
      transform: translateY(-2px);
      box-shadow: 0 8px 24px rgba(22, 114, 254, 0.18);
    }
    button:active {
      transform: translateY(0);
    }

    a {
      color: #6366f1;
      text-decoration: none;
      font-weight: 600;
      transition: all 0.2s ease;
      position: relative;
    }

    a:hover {
      color: #4f46e5;
    }

    a:after {
      content: '';
      position: absolute;
      width: 0;
      height: 2px;
      bottom: -2px;
      left: 0;
      background: #6366f1;
      transition: width 0.2s ease;
    }

    a:hover:after {
      width: 100%;
    }

    ol {
      margin-left: 10px;
      margin-bottom: 18px;
    }

    ol li {
      margin-left: 10px;
    }

    .info-box {
      background: linear-gradient(135deg, #eff6ff 0%, #f0f9ff 100%);
      border-left: 4px solid #6366f1;
      padding: 20px;
      margin: 24px 0;
      border-radius: 12px;
      border: 1px solid rgba(99, 102, 241, 0.1);
    }

    .info-box p {
      color: #1e40af;
      margin-bottom: 0;
    }

    .section-label {
      font-size: 12px;
      font-weight: 700;
      color: #6b7280;
      margin-bottom: 10px;
      text-transform: uppercase;
      letter-spacing: 1px;
    }

    .button-group {
      display: flex;
      align-items: center;
      gap: 16px;
      flex-wrap: wrap;
    }

    .analytics-badge {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
      color: white;
      padding: 6px 12px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 600;
      margin-bottom: 16px;
    }

    .feature-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 16px;
      margin: 24px 0;
    }

    .feature-item {
      padding: 16px;
      background: #f8fafc;
      border-radius: 10px;
      border: 1px solid #e2e8f0;
      text-align: center;
    }

    .feature-item strong {
      display: block;
      color: #6366f1;
      font-size: 14px;
      margin-bottom: 4px;
    }

    .metric-dot {
      width: 8px;
      height: 8px;
      background: #10b981;
      border-radius: 50%;
      display: inline-block;
      margin-right: 8px;
      animation: pulse 2s infinite;
    }

    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
  </style>

</head>

<body>
  <div class="container">
    <div class="header-brand">
      <div class="brand-dot"></div>
      <h1>Singular WebSDK Demo</h1>
    </div>

    <p>
      Below is a simple but comprehensive implementation using the documented Singular WebSDK API. 
      This sample provides clear separation for <strong>custom events</strong>, <strong>conversion events</strong>, <strong>revenue events</strong>, and <strong>web-to-app link support using WebSDK supported Web-to-App forwarding</strong>. </p>
      <p>You can run this code in a local HTML file or modify it in your site for advanced integration and troubleshooting.
    </p>

    <h2>SDK Initialization Testing</h2>
    <ol>
      <li>Open your browsers developer tools.</li>
      <li>Inspect the Network tab and filter for "sdk-api-v1.singular.net"</li>
      <li>Refresh this page, and examin the payload of the request.</li>
    </ol>


    <hr>

    <h2>User Authentication</h2>
    <p>User authentication is key to supporting cross-device attribution. Pass the common User ID value that would also be sent to Singular from mobile SDKs for cross-device continuity.</p>
    <div class="form-group">
      <div class="section-label">Simulate User Authentication</div>
      <input type="text" id="userId" placeholder="Enter a User ID"/>
    </div>
    <div class="form-group">
      <div class="button-group">
        <button onclick="setUserId()">Set the User ID (Login)</button>
        <button onclick="unsetUserId()">Unset the User ID (Logout)</button>
      </div>
    </div>

    <hr>

    <h2>SDK Event Testing</h2>
    <div class="form-group">
      <div class="section-label">Event Type Selection</div>
      <div class="radio-group">
        <label>
          <input type="radio" name="eventType" value="custom" checked onchange="handleRevenueFields(this)">
          <span>Custom Event</span>
        </label>
        <label>
          <input type="radio" name="eventType" value="conversion" onchange="handleRevenueFields(this)">
          <span>Conversion Event</span>
        </label>
        <label>
          <input type="radio" name="eventType" value="revenue" onchange="handleRevenueFields(this)">
          <span>Revenue Event</span>
        </label>
      </div>
    </div>

    <div class="form-group">
      <div class="section-label">Event Configuration</div>
      <input type="text" id="eventName" placeholder="Enter event name"/>
    </div>

    <div class="form-group" id="currencyGroup" style="display:none">
      <input type="text" id="currency" placeholder="Currency code (e.g., USD, EUR)"/>
    </div>

    <div class="form-group" id="amountGroup" style="display:none">
      <input type="text" id="amount" placeholder="Revenue amount (e.g., 29.99)"/>
    </div>

    <div class="form-group" id="attributesGroup" style="display:none">
      <input type="text" id="attributes" placeholder='Optional attributes (JSON, e.g. {"color":"blue"})'/>
    </div>

    <div class="form-group">
      <button id="send" onclick="sendCustomEvent()">Send Event to Singular</button>
    </div>

    <hr>
    <h2>Web-to-App Forwarding</h2>
    <div class="form-group">
    <ol>
      <li><a href="https://support.singular.net/hc/ko/articles/360042283811" target="_blank">Learn more about Web-to-App Forwarding</a></li>
    <li>In the page source code you will find the mobile web-to-app base link "<strong>https://seteam.sng.link/D0mvs/y3br?_smtype=3</strong>" used for demonstration purposes.</li>
    <li>Replace all occurances of the link with your own Singular Mobile Web-to-App link to test functionality.</li>
    <li>Add Singular Web Parameters to the URL and refresh the page. (example: "<strong>?wpsrc=SingularTest&wpcn=MyCampaign</strong>").</li>
  </ol>

    <hr>
    <div class="section-label">Mobile Web-to-App Test</div>
    <div class="form-group">
      <div class="button-group">
        <button onclick="displayWebLink()">Display Constructed Web-to-App Link</button>
        <button onclick="testWebLink()">Test Web-to-App Link</button>
      </div>
    </div>
  </div>
    <hr>
    <div class="section-label">Desktop Web-to-App Configuration</div>
    <div class="form-group">
    <p>Use a dynamic QR Code generation library like <a href="https://davidshimjs.github.io/qrcodejs/" target="_blank">QRCode.js</a> to build and display a QR code using the Singular Mobile Web-to-App link with constructed campaign parameters.</p>
    <div id="qrcode"></div>
    </div>

  </div>
  
  <script>
    /**
     * Initializes the Singular SDK with the provided configuration.
     * @param {string} sdkKey - The SDK key for Singular (e.g., 'se_team_9b3431b0').
     * @param {string} sdkSecret - The SDK secret for Singular (e.g., 'bcdee06e8490949422c071437da5c5ed').
     * @param {string} productId - The product ID for Singular (e.g., 'com.website').
     * @example
     * // Initialize the Singular SDK
     * initSingularSDK('se_team_9b3431b0', 'bcdee06e8490949422c071437da5c5ed', 'com.website');
     */
    function initSingularSDK() {
      var sdkKey = 'se_team_9b3431b0';
      var sdkSecret = 'bcdee06e8490949422c071437da5c5ed';
      var productId = 'com.website';

      // Global Properties set during init (plain object; max 5 keys persisted).
      const globalProperties = {
        plan: "pro",
        region: "us"
      };

      // Applies to the whole object: false = merge with existing, true = clear existing then set.
      const overrideExistingGlobalProperties = false; // required

      const config = new SingularConfig(sdkKey, sdkSecret, productId)
        .withLogLevel(3)
        .withSessionTimeoutInMinutes(0.5)
        .withGlobalProperties(globalProperties, overrideExistingGlobalProperties)
        .withInitFinishedCallback(function (initParams) {
          console.log("Singular Device ID:", initParams.singularDeviceId);
          console.log("Global props (SDK):", window.singularSdk.getGlobalProperties());

          // If you need to override just one key after init set it in the withInitFinishedCallback:
          window.singularSdk.setGlobalProperties("plan", "enterprise");
    });

      // Initialize the Singular SDK
      window.singularSdk.init(config);
      generateDesktopWebToAppQRCode(); // Generate QR code after initialization
    }

    /**
     * Triggers Singular SDK initialization when the DOM is fully parsed.
     * @example
     * document.addEventListener('DOMContentLoaded', function() {
     *   initSingularSDK();
     * });
     */
    document.addEventListener('DOMContentLoaded', function() {
      initSingularSDK();
    });


    // Fires a custom event using the Singular SDK
    function sendCustomEvent() {
      var eventName = document.getElementById('eventName').value;
      window.singularSdk.event(eventName);
    }

    // Fires a conversion event using the Singular SDK
    function sendConversionEvent() {
      var eventName = document.getElementById('eventName').value;
      window.singularSdk.conversionEvent(eventName);
    }

    // Fires a revenue event only once per session with deduplication
    // Parameters:
    //   - eventName: string (name of event), default "web_purchase" if blank
    //   - amount: revenue amount, default 0 if blank
    //   - currency: string, defaults to "USD" if blank
    //   - attributes: optional object with extra payload
    function sendRevenueEvent(eventName, amount, currency, attributes) {
      // Fill in defaults and normalize values
      eventName = eventName ? eventName : "web_purchase";
      currency = currency ? currency.toUpperCase() : "USD";
      amount = amount ? amount : 0;

      // Create a unique key based on event data for deduplication
      // btoa(JSON.stringify(...)) ensures order consistency in local/sessionStorage
      var payload = {
        eventName: eventName,
        amount: amount,
        currency: currency,
        attributes: attributes ? attributes : null
      };
      var storageKey = 'singular_revenue_' + btoa(JSON.stringify(payload));

      // Only fire event if no identical event has already fired in this tab/session
      if (!sessionStorage.getItem(storageKey)) {
        sessionStorage.setItem(storageKey, 'true');
        if (attributes && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
          // Fire event with attributes payload
          window.singularSdk.revenue(eventName, currency, amount, attributes);
        } else {
          // Fire simple revenue event
          window.singularSdk.revenue(eventName, currency, amount);
        }
        console.log("Revenue event sent:", payload);
      } else {
        // Duplicate detected, don't send to SDK
        console.log("Duplicate revenue event prevented:", payload);
        alert("Duplicate revenue event prevented!");
      }
    }

    // Collects form values, validates attributes JSON, and calls sendRevenueEvent()
    // Ensures only valid values are sent and blocks on malformed JSON
    function fireFormRevenueEvent() {
      var eventName = document.getElementById('eventName').value;
      var currency = document.getElementById('currency').value;
      var amount = document.getElementById('amount').value;
      
      var attributesRaw = document.getElementById('attributes').value;
      var attributes = null;
      if (attributesRaw) {
        try {
          // Try to parse the optional attributes field from JSON string
          attributes = JSON.parse(attributesRaw);
        } catch (e) {
          alert("Optional attributes must be valid JSON.");
          return; // Stop if invalid JSON
        }
      }

      // Calls main revenue logic for deduplication and event sending
      sendRevenueEvent(eventName, amount, currency, attributes);
    }

    // Controls which form fields are visible depending on selected event type
    // Revenue events require currency, amount, and attributes fields
    function handleRevenueFields(sender) {
      var isRevenue = sender.value === "revenue";
      document.getElementById("currencyGroup").style.display = isRevenue ? "block" : "none";
      document.getElementById("amountGroup").style.display = isRevenue ? "block" : "none";
      document.getElementById("attributesGroup").style.display = isRevenue ? "block" : "none";

      // Dynamically assign the event button's onclick handler
      var send = document.getElementById("send");
      send.onclick = (sender.value === "custom") ? sendCustomEvent
        : (sender.value === "conversion") ? sendConversionEvent
        : fireFormRevenueEvent; // Only fires revenue logic for "revenue"
    }

    // Opens demo Singular web-to-app link in a new tab/window
    function testWebLink() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      window.open(singularWebToAppBaseLink);
    }

    // Displays constructed Singular web-to-app link with campaign parameters
    function displayWebLink() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      var builtLink = window.singularSdk.buildWebToAppLink(singularWebToAppBaseLink);
      console.log("Singular Web-to-App Link: ", builtLink);
      alert("Singular Web-to-App Link: " + builtLink);
    }

    // Generates QR code for desktop deep linking using Singular Mobile Web-to-App link
    function generateDesktopWebToAppQRCode() {
      var singularWebToAppBaseLink = 'https://seteam.sng.link/D0mvs/y3br?_smtype=3';
      const value = window.singularSdk.buildWebToAppLink(singularWebToAppBaseLink);
      new QRCode(document.getElementById("qrcode"), {
        text: value,
        width: 128,
        height: 128,
        colorDark: "#000",
        colorLight: "#fff",
        correctLevel: QRCode.CorrectLevel.H
      });
    }

    // Simulate user authentication and send login event
    function setUserId() {
      var userId = document.getElementById('userId').value;
      window.singularSdk.login(userId);
      console.log("Singular User ID is Set to: " + userId);
      window.singularSdk.event("sng_login");
    }

    // Simulate user logout and unset Singular user ID
    function unsetUserId() {
      window.singularSdk.logout();
      console.log("Singular User ID is Unset");
    }
  </script>
</body>
</html>

고급 주제

Singular 배너

Singular 배너 활성화
#

참고: Singular 배너는 엔터프라이즈 기능입니다. 이 기능에 대한 자세한 내용은 고객 성공 매니저에게 문의하십시오.

Singular 배너는 모바일 웹사이트에 표시되어 웹 사용자를 원활하게 앱으로 유도하고 가장 관련성 높은 앱 콘텐츠를 표시할 수 있습니다. 웹사이트에서 Singular 배너를 활성화하면 조직에서 Singular 배너 UI를 통해 배너를 쉽게 디자인, 배포 및 유지 관리할 수 있습니다.

관련 문서

단계별 구현 가이드

  1. 웹사이트에 Singular 네이티브 자바Script WebSDK Script를 추가하세요.

    배너 구현을 진행하기 전에 위의 연동 가이드를따라 웹사이트에 Singular 네이티브 자바Script WebSDK를 추가하세요.

  2. Singular에 클라이언트 힌트 데이터 접근 권한 부여

    2023년 2월~3월 크로미움 기반 웹 브라우저에서 사용자 에이전트 데이터 제한이 시행됨에 따라, 광고주는 클라이언트 힌트 데이터를 수집해야 하며 Singular 웹SDK가 이 데이터를 수신할 수 있도록 권한을 부여해야 합니다. 자세한 내용은 배너 FAQ를 참조하십시오.

    웹사이트는 다음을 수행해야 합니다:

    • 클라이언트 힌트 요청 시작 (accept-ch header).
    • Singular에 권한 부여(제3자로서)하여 브라우저가 배너 요청 시 클라이언트 힌트를 Singular로 전송하도록 합니다 (permissions-policy header).

    페이지 로드 응답에 다음 응답 헤더를 추가하십시오:

    accept-ch:
    sec-ch-ua-model,
    sec-ch-ua-platform-version,
    sec-ch-ua-full-version-list
    
    permissions-policy:
    ch-ua-model=("https://sdk-api-v1.singular.net"),
    ch-ua-platform-version=("https://sdk-api-v1.singular.net"),
    ch-ua-full-version-list=("https://sdk-api-v1.singular.net")
  3. WebSDK 초기화에 배너 구성 옵션 추가

    Singular 배너는 모바일 웹사이트에 스마트 앱 다운로드 프롬프트를 표시하면서 원본 마케팅 소스의 어트리뷰션을 유지합니다. 웹-투-앱 지원이 활성화된 경우, SDK는 사용자를 사이트로 유입시킨 광고 네트워크 또는 캠페인을 추적한 후 후속 앱 설치를 해당 원본 소스로 어트리뷰션합니다.

    /**
     * Initialize Singular SDK with Banner support and web-to-app attribution.
     * This enables smart banners that direct mobile web users to download your app
     * while preserving the original marketing source (UTM parameters, ad network, etc.)
     * for proper attribution when the app is installed.
     * @param {string} sdkKey - Your Singular SDK Key
     * @param {string} sdkSecret - Your Singular SDK Secret
     * @param {string} productId - Your Product ID (e.g., com.your-website)
     */
    function initSingularSDK() {
      // Enable web-to-app tracking to preserve attribution data
      var bannersOptions = new BannersOptions().withWebToAppSupport();
      
      // Configure SDK with banner support
      var config = new SingularConfig(sdkKey, sdkSecret, productId)
        .withBannersSupport(bannersOptions);
      
      // Initialize the SDK
      window.singularSdk.init(config);
      
      // Display the smart banner
      window.singularSdk.showBanner();
    }
    
    // Call on DOM ready
    document.addEventListener('DOMContentLoaded', function() {
      initSingularSDK();
    });
  4. 페이지 경로에서 배너 재표시(Singular 페이지 애플리케이션 전용)

    애플리케이션이 Singular 페이지 애플리케이션인 경우, 각 페이지 경로에서 배너를 숨겼다가 다시 표시해야 합니다. 이렇게 하면 Singular가 웹 환경에 적합한 배너를 제공할 수 있습니다.

    배너를 숨기고 다시 표시하려면 다음 코드를 사용하세요:

    window.singularSdk.hideBanner();
    window.singularSdk.showBanner();
  5. [고급 옵션] 링크 설정 사용자 지정

    Singular는 코드를 통해 배너 내 링크를 개인화하는 방법을 제공합니다.

    링크를 맞춤 설정하려면:

    • LinkParams 객체를 생성하고 아래 함수 중 하나 이상을 사용하세요. window.singularSdk.showBanner() 호출 전에 수행해야 합니다.
    • 그런 다음 window.singularSdk.showBanner() 호출 시 LinkParams 객체를 전달하세요.

    예시:

    // Define a LinkParams object
    let bannerParams = new LinkParams();
    
    // Configure link options (see details on each option below)
    bannerParams.withAndroidRedirect("androidRedirectValue");
    bannerParams.withAndroidDL("androidDLParamValue");
    bannerParams.withAndroidDDL("androidDDLparamValue");
    bannerParams.withIosRedirect("iosRedirectValue");
    bannerParams.withIosDL("iosDLValue");
    bannerParams.withIosDDL("iosDDLValue");
    
    // Show the banner with the defined options
    window.singularSdk.showBanner(bannerParams);

    옵션 목록:

    메서드 설명
    withAndroidRedirect Android 앱 다운로드 페이지(일반적으로 Play 스토어 페이지)로 리디렉션 링크를 전달합니다.
    withAndroidDL Android 앱 내 페이지에 대한 딥 링크 전달.
    withAndroidDDL 지연된 딥 링크 전달, 즉 사용자가 아직 설치하지 않은 Android 앱 내 페이지로의 링크. (예: 사용자가 아직 설치하지 않은 앱의 특정 페이지로 연결)
    withIosRedirect iOS 앱 다운로드 페이지(일반적으로 App Store 페이지)로 리디렉션 링크 전달.
    withIosDL iOS 앱 내 페이지로 연결되는 딥 링크를 전달합니다.
    withIosDDL 사용자가 아직 설치하지 않은 iOS 앱 내 페이지로의 링크인 지연 딥 링크를 전달합니다.
  6. [고급 옵션] 코드를 사용하여 배너 표시/숨김 강제 설정

    3단계에서 언급한 바와 같이, Singular 페이지 애플리케이션의 경우 각 페이지 경로에서 hideBanner()showBanner() 메서드를 사용해야 적절한 배너가 제공되도록 보장할 수 있습니다(위 참조).

    hideBanner() 또한 showBanner() 메서드를 코드 전반에서 활용하여 표시될 예정이었던 배너를 숨기거나 숨겼던 배너를 다시 표시할 수 있습니다.

    메서드 설명
    singularSdk.hideBanner() 페이지에서 표시 중인 배너를 숨깁니다.
    singularSdk.showBanner() 사전 구성된 배너를 표시합니다.
    singularSdk.showBanner(params) 사전 구성된 배너를 표시하되 링크를 linkParams 객체에 정의된 옵션으로 재정의합니다 (4단계 참조).

전역 속성

글로벌 속성
#

Singular SDK를 사용하면 앱에서 전송되는 모든 세션 및 이벤트와 함께 Singular 서버로 전송될 사용자 정의 속성을 정의할 수 있습니다. 이러한 속성은 사용자, 앱 모드/상태 또는 기타 원하는 모든 정보를 표현할 수 있습니다.

  • 유효한 JSON 객체로 최대 5개의 글로벌 속성을 정의할 수 있습니다. 글로벌 속성은 브라우저의 localstorage 에 브라우저 컨텍스트가 변경되거나 캐시가 지워질 때까지 저장됩니다.

  • 각 속성 이름과 값은 최대 200자까지 가능합니다. 더 긴 속성 이름이나 값을 전달하면 200자로 잘립니다.

  • 글로벌 속성은 현재 Singular의 사용자 수준 이벤트 로그( '속성 로그 내보내기' 참조) 및 포스트백에 반영됩니다.

  • 글로벌 속성은 매칭이 필요한 경우 Singular에서 제3자로 전송되는 포스트백에서 사용할 수 있습니다.

웹SDK 초기화 시 글로벌 속성 설정을 지원하려면 config 객체에 ` .withGlobalProperties()` 옵션을 구현해야 합니다.

초기화 후 글로벌 속성을 처리하려면 SDK 함수인 ` setGlobalProperties()`, ` getGlobalProperties()`, ` clearGlobalProperties()`를 사용해야 합니다.

.withGlobalProperties()setGlobalProperties()getGlobalProperties()clearGlobalProperties()
/**
 * Set a Singular global property during SDK initialization.
 * Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
 * @param {{[key: string]: string}} globalProperties - The global property key/value pair object.
 * @param {string} propertyKey - The property key to set.
 * @param {string} propertyValue - The property value to set.
 * @param {boolean} overrideExisting - Whether to overwrite the property if it already exists.
 */
            
 // Global Properties set during init (plain object; max 5 keys persisted).
 var globalProperties = {
   propertyKey: propertyValue
 };

 // Set the override, applies to the whole object: false = merge with existing, true = clear existing then set.
 var overrideExisting = false; // required

 var config = new SingularConfig(sdkKey, sdkSecret, productId)
   .withLogLevel(3)
   .withSessionTimeoutInMinutes(0.5)
   .withGlobalProperties(globalProperties, overrideExisting)
   .withInitFinishedCallback(function (initParams) {
      console.log("Singular Device ID:", initParams.singularDeviceId);
      console.log("Global props (SDK):", window.singularSdk.getGlobalProperties());

      // If you need to override just one key after init set it in the withInitFinishedCallback:
      window.singularSdk.setGlobalProperties("plan", "enterprise");
  });

  // Initialize the Singular SDK
  window.singularSdk.init(config);           
xml-ph-0000@deepl.internal xml-ph-0000@deepl.internal

자연 검색 추적

자연 검색 추적 예시
#

중요! 본 예시는 대체 해결책으로 제공되어 자연 검색 추적을 활성화합니다. 해당 코드는 예시 용도로만 사용해야 하며, 마케팅 부서의 요구사항에 따라 웹 개발자가 업데이트 및 유지관리해야 합니다. 자연 검색 추적은 광고주마다 다른 의미를 가질 수 있습니다. 샘플을 검토하고 필요에 맞게 조정하십시오.

사용 이유

  • 캠페인 매개변수가 없더라도 자연 검색 방문이 정확히 추적되도록 보장합니다.

  • 명확한 기여도 측정을 위해 URL에 (리퍼러)의 값을 가진 Singular형 "Source" 매개변수 wpsrc및 "Campaign Name" 매개변수 wpcn 을 "OrganicSearch"로 추가합니다.

  • 나중에 사용할 수 있도록 현재 URL과 리퍼러를 localStorage에 저장합니다.

  • 순수 자바Script, 제로 의존성, 쉬운 연동.

작동 방식

  1. 페이지 URL에서 알려진 캠페인 매개변수(Google, Facebook, TikTok, UTM 등)를 확인합니다.

  2. 캠페인 매개변수가 없고 리퍼러가 검색 엔진인 경우 다음을 추가합니다:

    • wpsrc (리퍼러를 값으로)
    • wpcn (값으로 'OrganicSearch'를 사용)
  3. 페이지를 재로드하지 않고 브라우저의 URL을 업데이트합니다.

  4. 현재 URL과 리퍼러를 localStoragesng_urlsng_ref 으로 저장합니다.

사용법

  1. setupOrganicSearchTracking.js 를 사이트에 라이브러리로 추가합니다.
  2. WebSDK 초기화 전에 setupOrganicSearchTracking() 함수를 호출하세요.
setupOrganicSearchTracking.js
// singular-web-organic-search-tracking: setupOrganicSearchTracking.js
// Tracks organic search referrals by appending wpsrc and wpcn to the URL if no campaign parameters exist and the referrer is a search engine.

// Configuration for debugging (set to true to enable logs)
const debug = true;

// List of campaign parameters to check for exclusion
const campaignParams = [
    'gclid', 'fbclid', 'ttclid', 'msclkid', 'twclid', 'li_fat_id',
    'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'wpsrc'
];

// Whitelist of legitimate search engine domains (prevents false positives)
const legitimateSearchEngines = new Set([
    // Google domains
    'google.com', 'google.co.uk', 'google.ca', 'google.com.au', 'google.de', 
    'google.fr', 'google.it', 'google.es', 'google.co.jp', 'google.co.kr',
    'google.com.br', 'google.com.mx', 'google.co.in', 'google.ru', 'google.com.sg',
    
    // Bing domains  
    'bing.com', 'bing.co.uk', 'bing.ca', 'bing.com.au', 'bing.de',
    
    // Yahoo domains
    'yahoo.com', 'yahoo.co.uk', 'yahoo.ca', 'yahoo.com.au', 'yahoo.de',
    'yahoo.fr', 'yahoo.it', 'yahoo.es', 'yahoo.co.jp',
    
    // Other search engines
    'baidu.com', 'duckduckgo.com', 'yandex.com', 'yandex.ru',
    'ask.com', 'aol.com', 'ecosia.org', 'startpage.com', 
    'qwant.com', 'seznam.cz', 'naver.com', 'daum.net'
]);

// Extract main domain from hostname (removes subdomains)
function getMainDomain(hostname) {
    if (!hostname) return '';
    
    const lowerHost = hostname.toLowerCase();
    
    // Handle special cases for known search engines with country codes
    const searchEnginePatterns = {
        'google': (host) => {
            // Match google.TLD patterns more precisely
            if (host.includes('google.co.') || host.includes('google.com')) {
                const parts = host.split('.');
                const googleIndex = parts.findIndex(part => part === 'google');
                if (googleIndex !== -1 && googleIndex < parts.length - 1) {
                    return parts.slice(googleIndex).join('.');
                }
            }
            return null;
        },
        'bing': (host) => {
            if (host.includes('bing.co') || host.includes('bing.com')) {
                const parts = host.split('.');
                const bingIndex = parts.findIndex(part => part === 'bing');
                if (bingIndex !== -1 && bingIndex < parts.length - 1) {
                    return parts.slice(bingIndex).join('.');
                }
            }
            return null;
        },
        'yahoo': (host) => {
            if (host.includes('yahoo.co') || host.includes('yahoo.com')) {
                const parts = host.split('.');
                const yahooIndex = parts.findIndex(part => part === 'yahoo');
                if (yahooIndex !== -1 && yahooIndex < parts.length - 1) {
                    return parts.slice(yahooIndex).join('.');
                }
            }
            return null;
        }
    };
    
    // Try specific patterns for major search engines
    for (const [engine, patternFn] of Object.entries(searchEnginePatterns)) {
        if (lowerHost.includes(engine)) {
            const result = patternFn(lowerHost);
            if (result) return result;
        }
    }
    
    // Handle other known engines with simple mapping
    const otherEngines = {
        'baidu.com': 'baidu.com',
        'duckduckgo.com': 'duckduckgo.com', 
        'yandex.ru': 'yandex.ru',
        'yandex.com': 'yandex.com',
        'ask.com': 'ask.com',
        'aol.com': 'aol.com',
        'ecosia.org': 'ecosia.org',
        'startpage.com': 'startpage.com',
        'qwant.com': 'qwant.com',
        'seznam.cz': 'seznam.cz',
        'naver.com': 'naver.com',
        'daum.net': 'daum.net'
    };
    
    for (const [domain, result] of Object.entries(otherEngines)) {
        if (lowerHost.includes(domain)) {
            return result;
        }
    }
    
    // Fallback: Extract main domain by taking last 2 parts (for unknown domains)
    const parts = hostname.split('.');
    if (parts.length >= 2) {
        return parts[parts.length - 2] + '.' + parts[parts.length - 1];
    }
    
    return hostname;
}

// Get query parameter by name, using URL.searchParams with regex fallback for IE11
function getParameterByName(name, url) {
    if (!url) url = window.location.href;
    try {
        return new URL(url).searchParams.get(name) || null;
    } catch (e) {
        if (debug) console.warn('URL API not supported, falling back to regex:', e);
        name = name.replace(/[\[\]]/g, '\\$&');
        const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
        const results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, ' '));
    }
}

// Check if any campaign parameters exist in the URL
function hasAnyParameter(url, params) {
    return params.some(param => getParameterByName(param, url) !== null);
}

// Improved search engine detection - only checks hostname, uses whitelist
function isSearchEngineReferrer(referrer) {
    if (!referrer) return false;
    
    let hostname = '';
    try {
        hostname = new URL(referrer).hostname.toLowerCase();
    } catch (e) {
        // Fallback regex for hostname extraction
        const match = referrer.match(/^(?:https?:\/\/)?([^\/\?#]+)/i);
        hostname = match ? match[1].toLowerCase() : '';
    }
    
    if (!hostname) return false;
    
    // First check: exact match against whitelist
    if (legitimateSearchEngines.has(hostname)) {
        if (debug) console.log('Exact match found for:', hostname);
        return true;
    }
    
    // Second check: subdomain of legitimate search engine
    const hostParts = hostname.split('.');
    if (hostParts.length >= 3) {
        // Try domain.tld combination (e.g., google.com from www.google.com)
        const mainDomain = hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
        if (legitimateSearchEngines.has(mainDomain)) {
            if (debug) console.log('Subdomain match found for:', hostname, '-> main domain:', mainDomain);
            return true;
        }
        
        // Try last 3 parts for country codes (e.g., google.co.uk from www.google.co.uk)
        if (hostParts.length >= 3) {
            const ccDomain = hostParts[hostParts.length - 3] + '.' + hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
            if (legitimateSearchEngines.has(ccDomain)) {
                if (debug) console.log('Country code domain match found for:', hostname, '-> cc domain:', ccDomain);
                return true;
            }
        }
    }
    
    if (debug) {
        console.log('Hostname not recognized as legitimate search engine:', hostname);
    }
    
    return false;
}

// Main function to update URL with organic search tracking parameters
function setupOrganicSearchTracking() {
    const url = window.location.href;
    const referrer = document.referrer || '';

    // Store URL and referrer in localStorage
    try {
        localStorage.setItem('sng_url', url);
        localStorage.setItem('sng_ref', referrer);
    } catch (e) {
        if (debug) console.warn('localStorage not available:', e);
    }

    if (debug) {
        console.log('Current URL:', url);
        console.log('Referrer:', referrer);
    }

    // Skip if campaign parameters exist or referrer is not a search engine
    const hasCampaignParams = hasAnyParameter(url, campaignParams);
    if (hasCampaignParams || !isSearchEngineReferrer(referrer)) {
        if (debug) console.log('Skipping URL update: Campaign params exist or referrer is not a legitimate search engine');
        return;
    }

    // Extract and validate referrer hostname
    let referrerHostname = '';
    try {
        referrerHostname = new URL(referrer).hostname;
    } catch (e) {
        if (debug) console.warn('Invalid referrer URL, falling back to regex:', e);
        referrerHostname = referrer.match(/^(?:https?:\/\/)?([^\/]+)/i)?.[1] || '';
    }

    // Extract main domain from hostname
    const mainDomain = getMainDomain(referrerHostname);
    
    if (debug) {
        console.log('Full hostname:', referrerHostname);
        console.log('Main domain:', mainDomain);
    }

    // Only proceed if main domain is valid and contains safe characters
    if (!mainDomain || !/^[a-zA-Z0-9.-]+$/.test(mainDomain)) {
        if (debug) console.log('Skipping URL update: Invalid or unsafe main domain');
        return;
    }

    // Update URL with wpsrc and wpcn parameters
    const urlObj = new URL(url);
    
    // Set wpsrc to the main domain (e.g., google.com instead of tagassistant.google.com)
    urlObj.searchParams.set('wpsrc', mainDomain);
    
    // Set wpcn to 'Organic Search' to identify the campaign type
    urlObj.searchParams.set('wpcn', 'Organic Search');

    // Update the URL without reloading (check if history API is available)
    if (window.history && window.history.replaceState) {
        try {
            window.history.replaceState({}, '', urlObj.toString());
            if (debug) console.log('Updated URL with organic search tracking:', urlObj.toString());
        } catch (e) {
            if (debug) console.warn('Failed to update URL:', e);
        }
    } else {
        if (debug) console.warn('History API not supported, cannot update URL');
    }
}

크로스 서브도메인 추적

기본적으로 Singular WebSDK는 Singular Device ID를 생성하고 브라우저 스토리지를 사용하여 이를 유지합니다. 이 스토리지는 서브도메인 간에 공유될 수 없기 때문에, SDK는 각 서브도메인마다 새로운 ID를 생성하게 됩니다.

서브도메인 간에 Singular Device ID를 지속하려면 다음 옵션 중 하나를 사용할 수 있습니다:

방법 B (고급): Singular 디바이스 ID 수동 설정
#

방법 B (고급): Singular 기기 ID 수동 설정

Singular SDK가 기기 ID를 자동으로 저장하지 않도록 하려면, 도메인 간에 ID를 수동으로 저장할 수 있습니다. 예를 들어, 최상위 도메인 쿠키나 서버 측 쿠키를 사용하세요. 값은 Singular에서 이전에 생성된 유효한 uuid4 형식의 ID여야 합니다.

참고: Singular Device ID는 init 메서드 호출 후 또는 InitFinishedCallback을 사용하여 singularSdk.getSingularDeviceId()로 읽을 수 있습니다.

withPersistentSingularDeviceId 메서드

설명

지속적으로 유지하려는 Singular Device Id를 포함한 구성 옵션으로 SDK를 초기화합니다.

서명 withPersistentSingularDeviceId(singularDeviceId)
사용 예
function initSingularSDK() {
  const config = new SingularConfig(sdkKey, sdkSecret, productId)
    .withPersistentSingularDeviceId(singularDeviceId);
  window.singularSdk.init(config);
}

다음 단계

관련 문서