Web SDK - ネイティブ JavaScript 実装ガイド

文書

概要

INFO:Web Attributionはエンタープライズ向け機能です。アカウントでこの機能を有効にするには、カスタマーサクセスマネージャーにお問い合わせください。

このガイドでは、ネイティブ JavaScript を使用した Singular WebSDK の実装手順を説明します。この方法は最も信頼性の高いトラッキングを提供し、一般的な広告ブロッカーによってブロックされないため、ほとんどの実装で推奨されます。

重要!

  • ネイティブ JavaScript と Google タグマネージャーの両方を実装しないでください。重複トラッキングを避けるため、いずれか一方のみを選択してください。
  • Singular WebSDKはユーザーのブラウザでクライアントサイドに実行されるように設計されています。正常に機能するには、localStorageや Document Object Model(DOM)などのブラウザ機能へのアクセスが必要です。サーバーサイド(例:Next.js SSRやnode.js経由)でSDKを実行しようとしないでください。サーバー環境ではブラウザAPIへのアクセスが提供されないため、トラッキングが失敗します。

前提条件

開始前に、以下が用意されていることを確認してください:

  • SDKキーとSDKシークレット: 
  • プロダクトID:
    • 概要:ウェブサイト固有の名前。逆DNS形式(例:com.website-name )での使用が推奨されます。
    • 重要性:このIDはウェブサイトをSingular内のアプリとして関連付け、Singularのアプリページに記載されているWebアプリのbundleIDと一致させる必要があります。 
  • ウェブサイトのHTMLコードを編集する権限。
  • ページ内の「<head> 」セクションにJavaScriptを追加する権限。
  • 追跡したいイベントのリスト。アイデアについては、Singular 標準イベント:全リストおよび業種別推奨イベントをご覧ください。

実装手順


ステップ1: SDKライブラリスクリプトの追加

以下のコードスニペットを、ウェブサイトの全ページの<head> セクションに追加してください。可能な限り早い位置、理想的には<head> タグの直近に配置してください。

ヒント!スクリプトを早期に追加することで、ページソースコード内のSingular関数にSingular JavaScriptライブラリが確実に利用可能になります。

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. シングルページアプリケーション(SPA)の場合、最初のページロード時にSingular SDKを初期化し、その後、新しいページビューを表すルート変更のたびにSingularPage Visit関数window.singularSdk.pageVisit() を呼び出してください。
Basic InitializationNext.js / React

基本的なDOM準備完了時の初期化

イベントリスナーを追加し、以下のタイミングでinitSingularSDK()を呼び出す DOMContentLoaded

/**
 * 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)ルーティングを用いた基本初期化

シナリオ 対応方法

初回ページロード時

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キーに置き換えてください。
  • 'sdkSecret' を実際のSDKシークレットに置き換えてください。
  • Replace 'productId' 実際のプロダクトIDに置き換えてください。例:com.website-name 。Singularプラットフォームの「Apps」ページにあるBundleIDの値と一致している必要があります。

設定オプション

.with のメソッドをチェーンして追加機能を有効にし、WebSDK の設定を強化します。

例:- クッキーにSingularデバイス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) 自動クロスサブドメイン追跡を有効化 Cookieを使用した自動永続化

.withPersistentSingularDeviceId(singularDeviceId)

手動クロスサブドメイン追跡を有効にする SingularデバイスIDを手動で設定
.withInitFinishedCallback(callback) SDKの初期化が完了した際にコールバックを呼び出す 初期化完了時のコールバック関数呼び出し
.withGlobalProperties(globalProperties, false) 初期化時にグローバルプロパティを設定;第二引数は overrideExisting (ブール値)。 既存プロパティとのマージにはfalse を、 既存プロパティの上書きにはtrue を使用。 グローバルプロパティ
.withEventsDedupEnabled() 重複イベントのエクスポートを削減するオプションの重複排除を有効化 (例:短時間内に繰り返し発生するトリガー) イベント重複排除
.withTimeBetweenEvents(timeBetweenEvents) 重複排除の最大時間枠(ms)を設定(デフォルト: 1000ms / 1秒) イベント重複排除

開発者チェックリスト

  • SDK認証情報とプロダクトIDを準備する
  • カスタム設定(ユーザーID、タイムアウトなど)が必要かどうかを判断する。
  • 上記の例を使用して、Singular 初期化関数と SingularConfig オブジェクトを構築します。 ページロード時に初期化が一度だけトリガーされることを常にテストしてください。
  • ページロード時に初期化が1回のみ実行されることを必ずテストしてください。

TIP! オーガニック検索 トラッキングなどの高度な設定では、 クエリパラメータの調整など、 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、画像、スクリプト)のロード完了を検出します。 このイベント発生時、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値は、全プラットフォーム(Web/モバイル/PC/コンソール/オフライン)で取得する内部ユーザーIDと同一である必要があります。
  • SingularはユーザーIDをユーザーレベルエクスポート、ETL、および内部BIポストバック(設定済みの場合)に含めます。ユーザーIDはファーストパーティデータであり、Singularはこれを第三者と共有しません。
  • Singular SDKメソッドで設定されたユーザーID値は、logout() メソッドで解除されるまで、 または ブラウザのローカルストレージが削除されるまで持続します。 ウェブサイトの 閉じる/更新操作ではユーザーIDは解除されません。
  • プライベート/シークレットモードでは、ブラウザが終了時にローカルストレージを自動的に削除するため、SDKはユーザーIDを永続化できません。

ユーザーIDを設定するには、login() メソッドを使用します。 設定を解除するには(例:ユーザーがアカウントから「ログアウト」する場合)、 logout() メソッドを呼び出します。

注記:複数のユーザーが単一デバイスを使用する場合、ログイン/ログアウト時にユーザーIDを設定/解除するログアウトフローの実装を推奨します。

ウェブサイト上でSingular SDKを初期化する時点でユーザーIDが既知の場合、 configオブジェクト内でユーザーIDを設定してください。これにより、 Singularは最初のセッションからユーザーIDを取得できます。ただし、 ユーザーIDは通常、 ユーザーが登録またはログインを行います。その場合、登録フロー完了後にlogin() を呼び出してください。

TIP!モバイルSDKで使用しているのと同じカスタマーユーザーIDを使用してください。これによりクロスデバイスアトリビューションが可能になり、プラットフォームをまたいだユーザー行動の全体像を把握できます。

カスタマーユーザーIDのベストプラクティス:

  • ユーザーがログインまたはサインアップしたら直ちに設定する
  • Webとモバイルプラットフォームで同一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のWeb SDKは、短時間内に繰り返しトリガーされたことによる重複エクスポートを減らすためのオプションのイベント重複排除をサポートしています。有効にすると、SDKは設定された時間枠内で同じ重複排除パラメータに一致する重複イベントを削除します。

有効化方法

  • withEventsDedupEnabled: イベント重複排除を有効化(デフォルトは無効)。
  • withTimeBetweenEvents: 2つのイベントを重複と見なす最大時間枠(ミリ秒単位)。デフォルトは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はイベントの主要フィールドからハッシュ値を計算し、時間ウィンドウ内の重複を抑制します。重複排除パラメータには以下が含まれます:EventNameEventProductNameIsRevenueEventCustomUserIdGlobalPropertiesMatchIdWebUrl (またSDKはハッシュ化時にrevenue/argsなどの追加イベント「extra」ペイロードも考慮します)。


ステップ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コンソールでWeb SDK統合をテストできます。

  1. Singularプラットフォームで、[Developer Tools] > [Testing Console] に移動します。
  2. デバイスを追加」をクリックします。
  3. プラットフォーム「Web」を選択し、SingularデバイスIDを入力します。
  4. SingularデバイスIDはブラウザのペイロードから取得します。イベント内のSDID の位置については、上記のスクリーンショットを参照してください。
  5. テスト中はコンソールをアクティブ状態に保ち、別ウィンドウでテストを実行してください。コンソールが閉じられているかオフライン状態の場合、トリガーされたイベントは表示されません。
  6. SDIDを追加後、ウェブページを再読み込みしイベントをトリガーしてください。「__PAGE_VISIT__」およびその他のイベント(トリガーされた場合)がSDKコンソールに表示されます。
  7. テスト結果を確認する別の方法として、エクスポートログレポート&インサイトエクスポートログ)の利用があります。このデータセットは1時間遅延します。

ステップ7: Web-to-Appフォワーディングの実装

Web-to-Appアトリビューション転送

Singular WebSDKを使用して、ウェブサイトからモバイルアプリへのユーザージャーニーを追跡し、ウェブキャンペーンのモバイルアプリインストールおよび再エンゲージメントへの正確なアトリビューションを実現します。デスクトップユーザー向けのQRコードサポートを含む、Web-to-Appフォワーディングの設定手順に従ってください。

  1. モバイルウェブアトリビューション用にSingular WebSDKを設定するには、当社の「ウェブサイトからモバイルアプリへのアトリビューション転送ガイド」に従ってください。
  2. デスクトップ向けWebからアプリへのトラッキング:
    • 上記ガイドの手順を完了してください。
    • SingularモバイルWeb-to-Appリンク(例:https://yourlink.sng.link/... )と、QRCode.jsなどの動的QRコードライブラリを使用したWebSDK関数buildWebToAppLink(link) を利用します。
    • リンクをエンコードしたQRコードをウェブページ上に生成します。デスクトップユーザーがモバイル端末でスキャンするとアプリが起動し、アトリビューション用のキャンペーンデータが渡されます。
    • QRコード生成とWeb-to-Appリンク処理の実働例については、Singular WebSDKデモをご覧ください。

ヒント!Facebook、Instagram、TikTokなどで使用されるモバイルアプリ内ブラウザのWebビューは、ユーザーがデバイスのネイティブブラウザに移動するとSingularデバイスIDが変更され、アトリビューションが妨げられる可能性があります。

これを防ぐには、各広告ネットワークで適切なSingularトラッキングリンク形式を常に使用してください:


Singular WebSDKデモ

以下は、文書化されたSingular WebSDK APIを使用したシンプルでありながら包括的な実装例です。このサンプルでは、カスタムイベントコンバージョンイベント収益イベントおよびWebSDKがサポートするWeb-to-App転送を使用したWeb-to-Appリンクサポートが明確に分離されています。

このコードはローカル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/ja/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バナーを有効にする
#

INFO:シングルバナーはエンタープライズ向け機能です。 詳細についてはカスタマーサクセスマネージャーにお問い合わせください。

シングルバナーはモバイルウェブサイトに表示でき、 ウェブユーザーをシームレスにアプリへ誘導し、 最も関連性の高いアプリコンテンツを表示します。 ウェブサイトでシングルバナーを有効化すると、 組織はシングルバナーUIを通じてバナーを簡単に設計、 展開、維持できます。

関連記事

ステップバイステップ実装ガイド

  1. ウェブサイトにSingular Native JavaScript WebSDKスクリプトを追加してください。

    バナー実装を進める前に、上記の統合ガイドに従ってウェブサイトにSingularネイティブJavaScript WebSDKを追加してください。

  2. Singularにクライアントヒントデータへのアクセス権限を付与

    2023年2月~3月に導入されたChromiumベースの ウェブブラウザにおけるユーザーエージェントデータの制限により、広告主は クライアントヒントデータを取得するとともに、 Singular WebSDKがこのデータを受信する権限を付与する必要があります。詳細は バナーFAQ をご参照ください。

    ウェブサイトでは以下が必要です:

    • クライアントヒントの取得を開始する(accept-ch header )。
    • Singularに権限を付与(サードパーティとして)し、 ブラウザが バナー取得リクエスト時に 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初期化時にBanner設定オプションを追加

    Singularバナーは、元のマーケティングソースからのアトリビューションを維持しながら、 モバイルウェブサイトにスマートなアプリダウンロードプロンプトを表示します。 Web-to-Appサポートを有効にすると、 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がウェブ体験に適したバナーを配信します。 バナーの非表示と再表示には以下のコードを使用してください:

    バナーを非表示/再表示するには、以下のコードを使用してください:

    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ストアページ)へのリダイレクトリンクを渡します。 Androidアプリ内のページへのディープリンクを渡します。
    withAndroidDL Androidアプリ内のページへのディープリンクを渡す。
    withAndroidDDL 遅延ディープリンクを渡す。つまり、 ユーザーがまだインストールしていない Androidアプリ内のページへのリンク。
    withIosRedirect iOSアプリのダウンロードページ(通常はApp Storeページ)へのリダイレクトリンクを渡す。 ページ へのディープリンクを渡す。
    withIosDL iOSアプリ内のページへのディープリンクを渡す。
    withIosDDL ディレイドディープリンク(ユーザーがまだインストールしていないiOSアプリ内のページへのリンク)を渡す。
  6. [高度なオプション] コードを使用してバナーの非表示/表示を強制

    ステップ3で述べたように、シングルページアプリケーションの場合、 適切なバナーを配信するには、各ページルートで hideBanner() および showBanner() メソッドを使用する必要があります (上記参照)。

    hideBanner() また、showBanner() はコード全体で利用可能であり、 本来表示されるはずのバナーを非表示にしたり、 非表示にしたバナーを再表示したりする場合に使用できます。

    メソッド 説明
    singularSdk.hideBanner() ページから表示中のバナーを非表示にします。
    singularSdk.showBanner() 事前設定済みバナーを表示します。
    singularSdk.showBanner(params) 事前設定されたバナーを表示しますが、 リンク は linkParams オブジェクトで定義されたオプションで上書きします (ステップ 4 を参照)。

グローバルプロパティ

グローバルプロパティ
#

Singular SDK を使用すると、アプリから送信されるすべてのセッションおよびイベントとともに Singular サーバーに送信されるカスタムプロパティを定義できます。これらのプロパティは、ユーザー、アプリのモード/ステータス、その他任意の情報について、必要なあらゆる情報を表すことができます。

  • 有効なJSONオブジェクトとして最大5つのグローバルプロパティを定義できます。 グローバルプロパティは、ブラウザのlocalstorage に クリアされるかブラウザコンテキストが変更されるまで永続化されます。

  • 各プロパティ名と値は最大200文字までです。 これより長いプロパティ名や値を渡した場合、 200文字に切り詰められます。

  • グローバルプロパティは現在、Singularの ユーザーレベルイベントログ(アトリビューションログのエクスポートを参照) およびポストバックに反映されます。

  • グローバルプロパティは、マッチング目的で Singularからサードパーティへ送信されるポストバックにおいて 利用可能です。

WebSDK初期化時にグローバルプロパティの設定をサポートするには、 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

オーガニック検索トラッキング

オーガニック検索トラッキングの例
#

重要!この例は、 ワークアラウンド ソリューションとして提供され、オーガニック検索トラッキングを有効にするためのものです。コードは あくまで 例として使用し、マーケティング部門のニーズに基づいてウェブ開発者が更新・保守を行う必要があります。 オーガニック 検索トラッキングは広告主ごとに異なる意味を持つ場合があります。 サンプルを 確認し、必要に応じて調整してください。

なぜこれを使うのか?

  • キャンペーンパラメータが存在しない場合でも、 オーガニック検索からの訪問が適切に追跡されることを保証します。

  • 明確なアトリビューションのため、 URLに単数形「Source」パラメータwpsrc(リファラーからの値)と「Campaign Name」パラメータwpcn (「OrganicSearch」として)を追加します。

  • 現在のURLとリファラーをlocalStorageに保存し、 後で使用できるようにします。

  • 純粋なJavaScript、依存関係ゼロ、統合が容易。

動作原理

  1. ページURLから既知のキャンペーンパラメータ(Google、 Facebook、TikTok、UTMなど)をチェック。

  2. キャンペーンパラメータが存在せず、リファラーが 検索エンジンの場合、以下を追加:

    • wpsrc (リファラーを値として)
    • wpcn (値として OrganicSearch を設定)
  3. ページを再読み込みせずにブラウザのURLを更新します。

  4. 現在のURLとリファラーをlocalStoragesng_url およびsng_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デバイスIDを生成し、ブラウザストレージを使用して永続化します。このストレージはサブドメイン間で共有できないため、SDKは各サブドメインごとに新しいIDを生成します。

サブドメイン間でSingularデバイスIDを永続化したい場合は、以下のいずれかの方法を使用できます:

方法 B(上級者向け):SingularデバイスIDを手動で設定
#

方法 B(上級者向け):Singular デバイス ID の手動設定

Singular SDKによるデバイスIDの自動永続化を望まない場合、 ドメインを跨いで手動でIDを永続化できます。 例:トップレベルドメインの クッキーやサーバーサイドクッキーを使用。 値はSingularが事前に生成した 有効なuuid4形式のIDである必要があります。

注記:SingularデバイスIDは、 initメソッド呼び出し後またはInitFinishedCallback使用時に singularSdk.getSingularDeviceId()で取得可能です。

withPersistentSingularDeviceId メソッド

説明

設定オプション(永続化したいSingularデバイスIDを含む)でSDKを初期化します。

シグネチャ withPersistentSingularDeviceId(singularDeviceId)
使用例
function initSingularSDK() {
  const config = new SingularConfig(sdkKey, sdkSecret, productId)
    .withPersistentSingularDeviceId(singularDeviceId);
  window.singularSdk.init(config);
}

次のステップ

関連記事