웹 SDK - Google 태그 관리자 구현 가이드

문서

개요

INFO: 웹 어트리뷰션은 엔터프라이즈 기능입니다. 계정에 이 기능을 사용 설정하려면 고객 성공 매니저에게 문의하세요.

이 가이드는 Google 태그 관리자 (GTM)를 사용하여 Singular 웹 SDK를 구현하는 방법을 설명합니다. 이 방법은 웹사이트 코드에 직접 액세스할 수 없는 팀이나 GTM을 통해 추적을 관리하려는 팀에 이상적입니다.

중요! 동일한 사이트에서 GTM과 네이티브 자바Script 구현을 모두 사용하지 마세요. 중복 추적 및 이벤트 수 부풀리기를 방지하려면 한 가지 방법만 선택하세요. Singular 방식은 이벤트를 자동으로 중복 제거하지 않습니다.

  • 네이티브 자바Script와 구글 태그 관리자 방법을 모두 구현하지 마세요. 중복 추적을 방지하려면 하나만 선택하세요.
  • Singular 웹 SDK는 사용자의 브라우저에서 클라이언트 측에서 실행되도록 설계되었습니다. 제대로 작동하려면 localStorageDOM(문서 객체 모델) 과 같은 브라우저 기능에 액세스할 수 있어야 합니다. 서버 환경에서는 브라우저 API에 대한 액세스 권한을 제공하지 않으므로 추적 실패의 원인이 될 수 있으므로 SDK를 서버 측 (예: Next.js SSR 또는 node.js)에서 실행하려고 시도하지 마세요.

전제 조건

시작하기 전에 다음이 필요한지 확인하세요:

구현 단계


1단계: Singular 웹 트래킹 템플릿을 GTM 컨테이너에 추가하기

  1. Google 태그 관리자 계정에 로그인하고 웹사이트의 컨테이너를 선택합니다.
  2. 태그 > 새로 만들기로 이동합니다.
  3. 태그 이름을 지정합니다: "Singular 초기화 태그"
  4. 태그 구성 상자를 클릭하여 태그 설정을 시작합니다.
  5. 태그 유형: 을 선택하고 "커뮤니티 템플릿 갤러리에서 더 많은 태그 유형 찾기"를 선택합니다.
  6. "Singular" 를 검색하고 "Singular 웹 추적"을 선택합니다. "워크스페이스에 추가" 버튼을 클릭합니다.

2단계: SDK 초기화

  1. 양식 필드를 다음과 같이 작성합니다:
    • 트랙 유형을 초기화로 설정합니다.
    • API 키 필드에 실제 SDK 키를 입력합니다.
    • 비밀 필드에 실제 SDK 비밀을 입력합니다.
    • 실제 제품 ID를 입력합니다. com.website-name 와 같이 표시되어야 하며, Singular 플랫폼의 앱 페이지에 있는 BundleID 값과 일치해야 합니다.

      팁! 테스트에 특정 제품 ID( com.website-name.dev )를 사용하고 프로덕션에 푸시하기 전에 업데이트하세요. 이렇게 하면 Singular 보고에서 모든 테스트 데이터가 프로덕션 앱과 별도로 유지됩니다.

    • 선택사항입니다:
      • 로그 수준: 콘솔에 대한 SDK 디버그 로깅의 구성입니다. 기본값은 없음입니다.
      • 세션 시간 초과: SDK가 새 세션을 생성하기 전에 사용자가 비활성 상태여야 하는 시간입니다. Singular는 사용자 세션을 전송하여 사용자 리텐션을 계산하고 리인게이지먼트 어트리뷰션을 활성화합니다. 기본값은 30분입니다.
      • 교차 하위 도메인 추적
  2. 트리거링을 선택하여 이 태그가 작동하도록 트리거를 구성합니다.
  3. 새로 만들기를 선택하고 트리거의 이름을"Singular 초기화 트리거"로 지정합니다.
  4. 트리거 구성을 클릭하고"페이지 보기 - 창 로드됨"을 선택한 다음"저장"을 클릭합니다.
  5. "저장"을 다시 클릭하여 태그를 저장합니다.
  6. 태그 페이지에서"미리보기"를 클릭하고 Singular 초기화 태그가 트리거되는지 테스트합니다.

    singular_init_tag_fired.png

성공! 미리보기 콘솔의 태그 실행됨 섹션에"Singular 초기화 태그"가 표시되면 초기화 태그를 성공적으로 구성한 것입니다.

중요! SPA(Singular 페이지 애플리케이션)의 경우 다른 페이지로 라우팅할 때마다 페이지 방문 트랙타입을 트리거해야 합니다. 초기화가 이미 페이지 방문을 보고하므로 로드되는 첫 번째 페이지에서는 페이지 방문을 호출하지 마세요.

솔루션 개요

  • 사용자 정의 JavaScript 변수를 사용하여 첫 번째 페이지 로드인지 감지합니다.
  • 2개의 태그를 구성합니다:
    • "Singular 초기화태그"(트랙 유형 = 초기화)는 초기 페이지 로드 시에만 실행되도록 설정합니다.
    • "Singular 페이지 방문 태그"(트랙 유형 = 페이지 방문)는 기록 변경 트리거를 사용하여 모든 경로 변경 시(초기 로드 제외) 실행됩니다.
  • SPA가 경로 변경에 대해 히스토리 이벤트를 데이터 레이어에 푸시하는지 확인하세요.

image5.png


3단계: 이벤트 추적

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

중요! Singular는 중복 이벤트를 차단하지 않습니다! 페이지 새로 고침 또는 중복에 대한 보호 기능을 추가하는 것은 개발자의 책임입니다. 잘못된 구매 데이터를 방지하기 위해 구매 이벤트 전용 중복 제거 방법을 연동하는 것이 좋습니다. 예시는 아래의 '5단계: 중복 이벤트 방지'를 참조하세요.

Basic EventConversion EventRevenue Event

기본 이벤트 추적

간단한 이벤트를 추적하거나 유효한 JSON을 사용하여 사용자 지정 속성을 추가하여 이벤트에 대한 자세한 컨텍스트를 제공합니다:

  1. Singular 웹 추적 템플릿을 사용하여 사용자 지정 이벤트에 대한 새 태그를 만듭니다.

  2. 이벤트 태그의 이름을 지정합니다.

  3. 추적 유형 = 사용자 지정 이벤트를 선택합니다.

  4. "이벤트 이름" 필드를 조정하거나 적절한 변수를 설정합니다.

  5. "사용자 아이디" 필드를 조정하거나 적절한 변수를 설정합니다.

  6. 이벤트에 키/값 쌍을 전달하려면 "속성"을 조정합니다.

  7. 예상되는 경우에만 태그를 실행하는 데 적합한 트리거를 구성합니다.

  8. 트리거와 태그를 저장하고 미리보기에서 테스트합니다.

custom_event.png

일반적인 이벤트 구현 패턴

Page Load EventsButton Click EventsForm Submission Events

페이지 로드 이벤트용 GTM 트리거 만들기

구글 태그 매니저로 Singular 웹 SDK를 구현하려면 페이지가 로드될 때 실행되는 페이지 로드 트리거를 만들어야 합니다.

빠른 설정: GTM에서 트리거 새 트리거 구성으로 이동하여 트리거 유형으로 "페이지 보기"를 선택합니다. 대부분의 구현에서는 페이지가 로드될 때마다 트리거를 실행하려면 '모든 페이지 보기'를 선택합니다.

전체 설정 지침을 보려면: Google의 공식 태그 관리자 문서를 참조하세요:


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

Singular SDK 방법을 사용하여 내부 사용자 ID를 Singular로 보낼 수 있습니다.

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

참고: 여러 사용자가 하나의 디바이스를 사용하는 경우, 로그인과 로그아웃 시마다 사용자 ID를 설정해제하는 로그아웃 플로우를 구현하는 것을 권장합니다.

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

사용자가 로그인하지 않은 상태에서 웹사이트에서 작업을 수행하는 한, Singular에서 생성한 사용자 ID로 이벤트가 Singular로 전송됩니다. 그러나 사용자가 등록하거나 로그인한 후에는 웹사이트에서 사용되는 사용자 ID(예: 해시된 이메일 주소)와 함께 이벤트를 Singular로 전송할 수 있습니다.

Singular는 사용자 수준 데이터 내보내기( 어트리뷰션 로그 내보내기 참조)와 내부 BI 포스트백을 구성한 경우( 내부 BI 포스트백 구성 참조)에도 사용자 ID를 사용합니다.

사용자 ID를 Singular로 전송하는 방법에는 두 가지가 있습니다:

  • 권장: 웹사이트가 열릴 때 사용자 ID를 알고 있는 경우 SDK를 초기화할초기화 트랙 유형에서 사용자 ID를 설정합니다. 이렇게 하면 첫 페이지 방문부터 사용자 ID를 Singular에서 사용할 수 있습니다.
  • 또는 일반적으로 인증이 발생한 후 언제든지 트랙 유형 = 로그인으로 태그를 트리거할 수 있습니다. 사용자 ID를 사용할 수 있게 되는 즉시 호출하는 것이 좋습니다. 참고: 이 태그를 호출해도 이벤트가 트리거되지는 않습니다. 앞으로 모든 이벤트 트리거에 추가될 사용자 아이디만 설정할 뿐입니다!

사용자 아이디를 Singular로 설정하려면 '로그인' 트랙 유형으로 Singular 태그를 추가합니다:

  1. Google 태그 관리자 계정에서 태그 > 새로 만들기를 클릭합니다.
  2. 태그 구성 창에서 태그 구성을 클릭하고 태그 유형메뉴에서 "Singular 웹 추적"을 선택합니다.
  3. 추적 유형 아래에서 "로그인"을 선택합니다.
  4. 맞춤 사용자 아이디 아래에 사용자 아이디가 포함된 Google 태그 관리자 변수를 입력합니다.
  5. 트리거링을 클릭하고 트리거링 이벤트(사용자 로그인 또는 등록)를 추가합니다.
  6. 저장을 클릭합니다.

image4.png

사용자 ID를 설정 해제하려면" 로그아웃 " 트랙 유형으로 태그를 추가합니다:

  1. Google 태그 관리자 계정에서 태그 > 새로 만들기를 클릭합니다.
  2. 태그 구성 창에서 태그 구성을 클릭하고 태그 유형메뉴에서 "Singular 웹 추적"을 선택합니다.
  3. 추적 유형 아래에서 "로그아웃"을 선택합니다.
  4. 트리거링을 클릭하고 트리거링 이벤트인 사용자 로그아웃을 추가합니다.
  5. 저장을 클릭합니다.

image1.png

참고:

  • 사용자 ID는 로그아웃 트랙 유형을 사용하여 설정을 해제하거나 사용자가 로컬 저장소를 삭제할 때까지유지됩니다.
  • 웹사이트를 닫거나 새로고침해도 사용자 ID는 설정이 해제되지 않습니다.
  • 시크릿 등 비공개 모드로 브라우징하면 브라우저를 닫을 때 로컬 저장소가 자동으로 삭제되므로 Singular가 사용자 ID를 유지하지 못합니다.

5단계: 중복 이벤트 방지하기

Google 태그 관리자에서 중복 이벤트 방지하기

중요! 이것은 가장 일반적인 GTM 구현 오류 중 하나입니다. 적절한 보호 장치가 없으면 사용자가 페이지를 새로고침하거나 뒤로 이동하거나 작업을 다시 트리거할 때 Singular Web SDK 이벤트가 여러 번 발생하여 지표가 심각하게 부풀려질 수 있습니다.

GTM에서 중복이 발생하는 이유

Google 태그 관리자는 페이지가 로드될 때마다 트리거를 평가하여 사용자가 감사 페이지를 새로 고치거나 양식을 다시 제출하거나 브라우저 뒤로/앞으로 버튼을 사용하여 탐색할 때 태그가 다시 실행되도록 합니다. 이는 GTM에 사용자 지정 이벤트에 대한 기본 제공 세션 인식 기능이 없기 때문에 발생합니다.


GTM 중복 제거 방법

세션 저장 접근 방식: 브라우저의 세션 저장소에 고유 이벤트 식별자를 저장하여 사용자 세션 중에 어떤 이벤트가 이미 실행되었는지 추적합니다.

커스텀 자바Script 변수: 새로운 이벤트가 트리거되도록 허용하기 전에 localStorage에서 이전에 실행된 이벤트를 확인하는 변수를 생성합니다.

트리거 조건: 트리거에 조건부 로직을 추가하여 중복 지표가 있을 때 트리거가 실행되지 않도록 합니다.


구현 단계

저장소 변수 만들기: 이벤트 매개변수와 사용자 세션을 기반으로 각 Singular 이벤트에 대해 고유 식별자를 생성하는 사용자 지정 자바Script 변수를 빌드합니다.

검사 변수 빌드: 현재 이벤트 식별자가 브라우저 저장소에 이미 존재하는지 확인하는 또 다른 사용자 지정 자바Script 변수를 만듭니다.

트리거 조건 추가: "중복 확인" 변수가 "false"와 같다는 조건을 포함하도록 Singular 웹 SDK 트리거를 수정합니다.

발동 후 저장: GTM의 태그 시퀀싱 기능을 사용하여 Singular 이벤트가 성공적으로 실행된 후 세션스토리지에 이벤트 식별자를 저장하는 사용자 지정 HTML 태그를 실행합니다.


GTM 관련 고려 사항

태그 시퀀싱: 고급 설정 태그 시퀀싱을 사용하여 기본 Singular 태그가 완료된 후 스토리지 태그가 실행되도록 하세요.

변수 평가: 사용자 정의 JavaScript 변수는 참조될 때마다 평가되므로 성능에 맞게 최적화하세요.

교차 도메인 제한: 세션 스토리지는 도메인별로 다르므로 필요한 경우 교차 도메인 추적을 위한 쿠키 기반 솔루션을 구현하세요.

자세한 구현 방법은: 포괄적인 GTM 중복 제거 가이드를 참조하세요:


6단계: GTM 구현 테스트하기

  1. GTM 미리보기 모드를 열고 웹사이트를 로드합니다.
  2. 확인합니다:
  • SDK가 로드됩니다( singular-sdk.js 로 네트워크 요청).
  • 이벤트가 예상대로 트리거되며 액션당 한 번만 트리거됩니다.
  • 브라우저 콘솔에 singular관련 오류가 없습니다.
  • 네트워크 요청이 sdk-api-v1.singular.net으로 전송됩니다.
  1. 브라우저 개발자 도구의 네트워크 탭을 사용하여 적절한 페이로드가 전송되었는지 확인합니다.

성공! 네트워크 요청에 올바른 Singular 이벤트가 표시되고 중복이 없으면 라이브 준비가 완료된 것입니다!


7단계: 웹-투-앱 포워딩 구현하기

웹-투-앱 어트리뷰션 포워딩

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

  1. 웹사이트-모바일 앱 어트리뷰션 포워딩 가이드에따라 모바일 웹 어트리뷰션을 위한 Singular 웹 SDK를 구성하세요.
  2. 데스크톱 웹-투-앱 추적용:
    • QR코드 생성기 정리 태그 생성
      1. GTM에서 태그 > 새로 만들기로 이동하여 사용자 지정 HTML을 선택합니다.
      2. 코드를 추가합니다:
        • 선택한 QR 코드 라이브러리를 삽입합니다.
        • window.singularSdk.buildWebToAppLink(baselink); 에서 웹-투-앱 링크를 검색합니다.
        • 반환된 링크에서 QR코드를 생성합니다.
        • 페이지에서 QRCode 이미지를 업데이트합니다.
      3. 태그 이름을 지정합니다: "Singular - QR 코드 생성기(정리)"
      4. 트리거가 필요하지 않습니다: 정리 태그는 기본 태그에서 트리거를 상속합니다.
      5. Singular 초기화 태그를 구성합니다:
        • 태그 구성을클릭합니다.
        • 고급 설정 > 태그 시퀀싱으로이동합니다.
        • "[Singular 초기화 태그] 실행 후 정리 태그 실행"을 선택합니다.
        • 드롭다운에서 QR 코드 생성기 태그를 선택합니다.
        • 태그를 저장하고 미리보기 모드에서 테스트합니다.

팁! 모바일 인앱 브라우저 웹 뷰(예: 페이스북, 인스타그램, 틱톡)는 사용자가 디바이스의 기본 브라우저로 이동하면 Singular 디바이스 ID가 변경되어 어트리뷰션이 중단될 수 있습니다.

이를 방지하려면 항상 각 광고 네트워크에 적합한 Singular 추적 링크 형식을 사용하세요:


고급 주제

글로벌 속성 추가하기

글로벌 속성
#

Singular SDK를 사용하면 앱에서 전송되는 모든 세션 및 이벤트와 함께 Singular 서버로 전송할 사용자 지정 프로퍼티를 정의할 수 있습니다. 이러한 프로퍼티는 사용자, 앱 모드/상태 등 원하는 모든 정보를 나타낼 수 있습니다.

  • 유효한 JSON 객체로 최대 5개의 글로벌 프로퍼티를 정의할 수 있습니다. 전역 속성은 지워지거나 브라우저 컨텍스트가 변경될 때까지 브라우저 localstorage 에 유지됩니다.

  • 각 속성 이름과 값은 최대 200자까지 입력할 수 있습니다. 더 긴 속성 이름이나 값을 전달하면 200자로 잘립니다.

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

  • 글로벌 속성은 Singular에서 서드파티로 전송된 포스트백에서 매칭을 위해 필요한 경우 사용할 수 있습니다.

현재 구글 태그 관리자 초기화 태그에서는 SDK 초기화 전에 글로벌 프로퍼티를 설정하는 것이 지원되지 않습니다. 그러나 초기화 후 글로벌 프로퍼티를 처리하려면 사용자 지정 HTML 태그를 생성하고 네이티브 자바Script 함수를 실행해야 합니다: setGlobalProperties(), getGlobalProperties(), clearGlobalProperties().

글로벌 속성 처리를 위한 사용자 정의 HTML 태그 만들기

  1. Google 태그 관리자 인터페이스의 왼쪽 탐색 메뉴에서 태그를 클릭한 다음 새로 만들기 버튼을 클릭하여 새 태그를 만듭니다.

  2. 태그 구성을 클릭하고 사용 가능한 태그 유형 목록에서 맞춤 HTML을 선택합니다.

  3. HTML 필드에 글로벌 속성 메서드 코드를 붙여넣습니다. 아래 옵션을 참조하세요:

  4. 트리거링을 클릭하고 태그에 트리거 옵션을 할당합니다.

  5. 상단의 태그 이름 필드에 "Singular - 전역 속성 설정"과 같이 태그에 설명이 포함된 이름을 지정합니다.

전역 속성을 설정하면 설정이 해제되거나 지워질 때까지 모든 이벤트에 전역 속성이 유지됩니다.

미리 보기를 클릭하여 구현을 테스트합니다. 브라우저 콘솔에서 전역 속성이 설정되었는지 확인합니다.

고급 구성의 경우: Google의 공식 맞춤 HTML 문서를 참조하세요.

Setting Global PropertiesGetting GlobalPropertiesClearing Global Properties

전역 속성 설정을 위한 사용자 정의 HTML 태그 코드

<script>
/**
 * Set a Singular global property before SDK initialization.
 * Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
 * 
 * @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.
 */

// Example usage - customize these values for your implementation
window.singularSdk.setGlobalProperties('user_type', 'premium', true);
window.singularSdk.setGlobalProperties('app_version', '2.1.0', false);
</script>

자연 검색 추적

자연 검색 추적 예시
#

자연 검색 추적 설정 태그 만들기

중요!- 이 태그는 Singular SDK 초기화 전에 실행되어야합니다!

이 사용자 정의 HTML 태그는 문서 URL을 수정하여 Singular 웹 SDK가 초기화 중에 읽어야 하는 자연 검색 추적 매개변수(wpsrc 및 wpcn)를 추가합니다. 태그 순서는 적절한 실행 순서를 보장하기 위해 필수적입니다.

이 예제는 자연 검색 추적을 활성화하기 위한 해결 방법으로 제공됩니다. 이 코드는 예시로만 사용하고 마케팅 부서의 필요에 따라 웹 개발자가 업데이트 및 유지 관리해야 합니다. 자연 검색 추적은 광고주마다 다른 의미를 가질 수 있습니다. 샘플을 검토하여 필요에 맞게 조정하시기 바랍니다.

왜 사용하나요?

  • 캠페인 매개변수가 없는 경우에도 자연 검색 방문이 제대로 추적되도록 합니다.

  • 명확한 어트리뷰션을 위해 Singular '소스' 파라미터 wpsrc에 (리퍼러)의 값과 '캠페인 이름' 파라미터 wpcn 를 'OrganicSearch'로 URL에 추가합니다.

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

  • 순수 자바Script, 제로 종속성, 손쉬운 연동.

작동 방식

  1. 페이지 URL에서 (구글, 페이스북, 틱톡, UTM 등) 알려진 캠페인 파라미터가 있는지 확인합니다.

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

    • wpsrc (리퍼러를 값으로)
    • wpcn (해당 값으로 OrganicSearch 포함)
  3. 페이지를 다시 로드하지 않고 브라우저에서 URL을 업데이트합니다.

  4. localStorage 의 현재 URL과 리퍼러를 sng_urlsng_ref 로 저장합니다.

사용 방법

  1. GTM에서 태그 > 새로 만들기로 이동하여 태그 구성을 클릭한 다음 사용자 정의 HTML을 선택합니다.
  2. 아래와 같이 전체 JavaScript 코드를 붙여넣습니다.
  3. 이 태그에 대한 트리거 생성은 건너뜁니다.이 태그는 Singular 초기화 태그보다 먼저 실행되어야 하므로 태그 시퀀싱을 사용하여 초기화 전에 실행되도록 구성합니다.

    1. "Singular - 오가닉 검색 설정"과 같은 설명이 포함된 이름을 사용합니다.
    2. Singular 웹 SDK 초기화 태그를 엽니다.
    3. 태그 구성을 클릭합니다.
    4. 고급 설정 > 태그 시퀀싱으로 이동합니다.
    5. "[Singular 초기화 태그]가 실행되기 전에 설정 태그 실행"을 선택합니다.
    6. 드롭다운에서 "Singular - 자연 검색 설정" 태그를 선택합니다.
    7. 권장: 불완전한 URL 수정으로 초기화되지 않도록 하려면 "[설정 태그]가 실패하면 [Singular 초기화 태그]를 실행하지 않음"에 체크하세요.
    8. organic_search.png
    9. 태그를 저장합니다.
  4. GTM의 미리보기 모드를 사용하여 확인합니다:

    • 설정 태그가 먼저 실행되어 URL을 수정합니다.
    • Singular 초기화 태그가 두 번째로 실행되어 수정된 URL 매개변수를 읽습니다.
    • 태그 간에 타이밍 충돌이 발생하지 않습니다.

고급 태그 시퀀싱의 경우: Google의 공식 문서를 참조하세요:


Organic Search Tracking Code
<script>
(function() {
    // 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)
    var debug = true;

    // List of campaign parameters to check for exclusion
    var 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)
    var 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 '';
        
        var lowerHost = hostname.toLowerCase();
        
        // Handle special cases for known search engines with country codes
        var searchEnginePatterns = {
            'google': function(host) {
                // Match google.TLD patterns more precisely
                if (host.indexOf('google.co.') !== -1 || host.indexOf('google.com') !== -1) {
                    var parts = host.split('.');
                    for (var i = 0; i < parts.length - 1; i++) {
                        if (parts[i] === 'google') {
                            return parts.slice(i).join('.');
                        }
                    }
                }
                return null;
            },
            'bing': function(host) {
                if (host.indexOf('bing.co') !== -1 || host.indexOf('bing.com') !== -1) {
                    var parts = host.split('.');
                    for (var i = 0; i < parts.length - 1; i++) {
                        if (parts[i] === 'bing') {
                            return parts.slice(i).join('.');
                        }
                    }
                }
                return null;
            },
            'yahoo': function(host) {
                if (host.indexOf('yahoo.co') !== -1 || host.indexOf('yahoo.com') !== -1) {
                    var parts = host.split('.');
                    for (var i = 0; i < parts.length - 1; i++) {
                        if (parts[i] === 'yahoo') {
                            return parts.slice(i).join('.');
                        }
                    }
                }
                return null;
            }
        };
        
        // Try specific patterns for major search engines
        for (var engine in searchEnginePatterns) {
            if (lowerHost.indexOf(engine) !== -1) {
                var result = searchEnginePatterns[engine](lowerHost);
                if (result) return result;
            }
        }
        
        // Handle other known engines with simple mapping
        var 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 (var domain in otherEngines) {
            if (lowerHost.indexOf(domain) !== -1) {
                return otherEngines[domain];
            }
        }
        
        // Fallback: Extract main domain by taking last 2 parts (for unknown domains)
        var 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, '\\$&');
            var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
            var 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) {
        for (var i = 0; i < params.length; i++) {
            if (getParameterByName(params[i], url) !== null) {
                return true;
            }
        }
        return false;
    }

    // Improved search engine detection - only checks hostname, uses whitelist
    function isSearchEngineReferrer(referrer) {
        if (!referrer) return false;
        
        var hostname = '';
        try {
            hostname = new URL(referrer).hostname.toLowerCase();
        } catch (e) {
            // Fallback regex for hostname extraction (IE11 compatibility)
            var 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
        var hostParts = hostname.split('.');
        if (hostParts.length >= 3) {
            // Try domain.tld combination (e.g., google.com from www.google.com)
            var 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) {
                var 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() {
        var url = window.location.href;
        var 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
        var 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
        var referrerHostname = '';
        try {
            referrerHostname = new URL(referrer).hostname;
        } catch (e) {
            if (debug) console.warn('Invalid referrer URL, falling back to regex:', e);
            var match = referrer.match(/^(?:https?:\/\/)?([^\/]+)/i);
            referrerHostname = match ? match[1] : '';
        }

        // Extract main domain from hostname
        var 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
        var urlObj;
        try {
            urlObj = new URL(url);
        } catch (e) {
            if (debug) console.warn('URL API not supported, cannot modify URL:', e);
            return;
        }
        
        // 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');
        }
    }

    // Execute the function
    setupOrganicSearchTracking();
})();
</script>

교차 하위 도메인 추적

기본적으로 Singular 웹사이트 SDK는 Singular 디바이스 ID를 생성하고 브라우저 저장소를 사용하여 이를 유지합니다. 이 저장소는 하위 도메인 간에 공유할 수 없으므로 SDK는 각 하위 도메인에 대해 새 ID를 생성하게 됩니다.

하위 도메인에서 Singular 장치 ID를 유지하려면 다음 옵션 중 하나를 사용할 수 있습니다:

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

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

Singular SDK가 자동으로 디바이스 ID를 유지하지 않도록 하려면 최상위 도메인 쿠키 또는 서버 측 쿠키를 사용하여 도메인 전체에서 수동으로 ID를 유지할 수 있습니다. 이 값은 이전에 Singular에서 유효한 uuid4 형식으로 생성한 ID여야 합니다.

참고: 사용자 지정 JavaScript 변수를 정의하고 Init 트랙 유형 태그를 호출한 후 singularSdk.getSingularDeviceId()를 호출하여 Singular 디바이스 ID를 읽을 수 있습니다.

mceclip2.png


일반적인 GTM 구현 문제

필수 디바이스 식별자
이벤트가 여러 번 실행됨 트리거를 구체화하거나 제한하고, '페이지당 한 번'을 사용하거나 조건을 추가하세요.
잘못된 제품 ID 형식 제품 ID는 역 DNS 표기법(com.company.site)을 사용해야 합니다.
추적되지 않는 이벤트 이벤트 이름의 철자와 대소문자를 확인하고, 이벤트 태그가 실행되기 전에 singularSDK가 로드되었는지 확인합니다.
SDK Script 차단 광고 차단기 또는 제한적인 콘텐츠 보안 정책; 문제가 지속되는 경우 네이티브 JS 구현으로 전환하는 것을 고려하세요.

모범 사례

  • GTM을 체계적으로 관리하세요: 태그와 트리거의 이름을 명확하게 지정하고 그 목적을 문서화하세요.
  • 사용하지 않는 태그 또는 레거시 태그를 정기적으로 감사합니다.
  • 트리거 수를 최소화하여 중복 이벤트의 위험을 줄입니다.
  • 변경 후에는 항상 GTM의 미리보기 모드에서 철저히 테스트한 후 라이브로 푸시하세요.
  • 쿠키 기반 추적(교차 하위 도메인 추적)을 사용하는 경우 개인정보처리방침을적절히 업데이트하세요.

관련 문서