概要
情報Web Attribution は企業向けの機能です。お客様のアカウントでこの機能を有効にするには、カスタマーサクセスマネージャーにお問い合わせください。
このガイドでは、ネイティブJavaScriptを使用したSingular WebSDKの実装方法を説明します。この方法は最も信頼性の高いトラッキングを提供し、一般的な広告ブロッカーによってブロックされないため、ほとんどの実装に推奨されます。
重要
- ネイティブ JavaScript と Google タグマネージャの両方の方法を実装しないでください。重複トラッキングを避けるため、どちらか一方のみを選択してください。
- Singular WebSDKはユーザーのブラウザでクライアントサイドで動作するように設計されています。正しく機能するためにはlocalStorageや DOM(Document Object Model)などのブラウザ機能にアクセスする必要があります。サーバー環境ではブラウザAPIにアクセスできないため、トラッキングに失敗します。
前提条件
開始する前に、以下を確認してください:
-
SDKキーとSDKシークレット
- 入手方法Singularアカウントにログインし、Developer Tools > SDK Integration > SDK Keysに移動します。
-
プロダクトID
-
何ですか?お客様のウェブサイトのユニークな名前です。理想的には逆DNS形式(例:
com.website-name)を使用します。 - なぜ重要なのか:このIDはウェブサイトをSingularのAppとして関連付け、SingularのAppsページに記載されているWeb AppbundleIDと一致する必要があります。
-
何ですか?お客様のウェブサイトのユニークな名前です。理想的には逆DNS形式(例:
- ウェブサイトのHTMLコードを編集する権限。
- ページの
<head>セクションに JavaScript を追加するアクセス権。 - 追跡したいイベントのリスト。Singular 標準イベントをご覧ください:全イベントリストと分野別推奨イベントをご覧ください。
実装ステップ
ステップ1:SDKライブラリスクリプトの追加
ウェブサイトの各ページの<head> セクションに以下のコードスニペットを追加します。できるだけ早い段階で、理想的には<head> タグの上部付近に配置します。
ヒントスクリプトを早めに追加することで、ページのソースコードにあるSingular関数でSingular JavaScriptライブラリが利用できるようになります。
<script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>
WebSDK JavaScript ライブラリのパスに特定のバージョンを指定します:
<script src="https://web-sdk-cdn.singular.net/singular-sdk/1.4.3/singular-sdk.js"></script>
-
プロジェクトのルートディレクトリで
npm i singular-sdkを実行するか、package.jsonファイルの dependencies セクションに"singular-sdk": "^1.4.3"を追加してからnpm installを実行してください。 -
SDKを使用したいスクリプトに、以下のコードを追加してください。
import {singularSdk, SingularConfig} from "singular-sdk";
Next.js / Reactでの使用
Next.jsはサーバーサイドレンダリング(SSR)、静的サイト生成(SSG)、React Server Componentsを提供するReactフレームワークです。 Singular WebSDKはブラウザAPI(DOM、localStorage、Cookie)を必要とするため、クライアントサイドでのみロードする必要があります。
重要:Singular SDKをサーバーサイドのコード(getServerSideProps 、React Server Components、Node.js環境など)では絶対にロードしないでください。ブラウザAPIがサーバー上で利用できないため、エラーの原因となります。
方法1:Next.jsスクリプトコンポーネントを使用する(推奨)
Next.jsの<Script> コンポーネントは、最適化されたロード戦略を提供し、ルート変更に伴うスクリプトの重複インジェクションを防ぎます。
// pages/_app.tsx (Pages Router) or app/layout.tsx (App Router)
import Script from 'next/script'
export default function App({ Component, pageProps }) {
return (
<>
<Script
src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"
strategy="afterInteractive"
/>
<Component {...pageProps} />
</>
)
}
スクリプト戦略のオプション
-
afterInteractive(推奨):ページがインタラクティブになった後にロード - アナリティクスとトラッキングに最適です。 -
lazyOnloadブラウザのアイドル時間中にロード - 重要でないウィジェットに使用します。
方法 2: コンポーネントレベルのロードのための動的インポート
コンポーネントレベルの制御には、Next.jsのダイナミックインポートをssr: false 、必要なときだけスクリプトを読み込みます:
// pages/index.tsx
import dynamic from 'next/dynamic'
const SingularSDKLoader = dynamic(
() => import('../components/SingularSDKLoader'),
{ ssr: false }
)
export default function Page() {
return (
<div>
<SingularSDKLoader />
<h1>Your Page Content</h1>
</div>
)
}
// components/SingularSDKLoader.tsx
'use client' // Required for Next.js App Router
import { useEffect } from 'react'
export default function SingularSDKLoader() {
useEffect(() => {
// Load Singular SDK script dynamically
const script = document.createElement('script')
script.src = 'https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js'
script.async = true
document.body.appendChild(script)
// Cleanup on unmount
return () => {
if (document.body.contains(script)) {
document.body.removeChild(script)
}
}
}, [])
return null
}
環境変数のセットアップ
ヒントSingularの認証情報をブラウザで利用できるように、NEXT_PUBLIC_ をプレフィックスとする環境変数に保存します:
# .env.local
NEXT_PUBLIC_SINGULAR_SDK_KEY=your_sdk_key_here
NEXT_PUBLIC_SINGULAR_SDK_SECRET=your_sdk_secret_here
NEXT_PUBLIC_SINGULAR_PRODUCT_ID=com.your-website
これらの環境変数はクライアントサイドのコードでアクセスできるようになり、ステップ2の初期化の際に使用できます。
アプリルーターとページルーターの比較
| ルーターの種類 | 主な違い | スクリプトの場所 |
|---|---|---|
| アプリルーター(Next.js 13以上) |
コンポーネントはデフォルトでサーバーコンポーネントブラウザAPI用に'use client' ディレクティブを追加。 |
app/layout.tsx
|
| ページルーター(Next.js 12以前) | すべてのコンポーネントはデフォルトでクライアントコンポーネントです。 |
pages/_app.tsx
|
ステップ2: SDKの初期化
- ブラウザにページがロードされるたびに、SDKを常に初期化します。
- 初期化はすべてのSingularアトリビューションとイベントトラッキング機能に必要です。
- 初期化は
__PAGE_VISIT__イベントが発生します。 - この
__PAGE_VISIT__イベントは、以下の条件が満たされたときにサーバーサイドで新しいセッションを生成するために使用されます:- ユーザーが URL に新しい広告データ(UTM や WP パラメータなど)を持って到着した場合。
- 前のセッションの有効期限が切れた(30分間操作がなかった場合)。
- セッションは、ユーザーのリテンションを測定し、リエンゲージメントアトリビューションをサポートするために使用されます。
- 初期化関数を作成し、ページロード後の DOM Ready でこの関数を呼び出します。
- 他のSingularイベントが報告される前に初期化が行われるようにします。
-
シングルページアプリケーション(SPA)の場合は、最初のページロード時にSingular SDKを初期化し、新しいページビューを表すルート変更ごとにSingular Page Visist関数
window.singularSdk.pageVisit()。
基本的な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)ルーティングによる基本的な初期化
| シナリオ | 何をすべきか |
|---|---|
|
最初のページロード |
|
|
新しいルート/ページへの移動 |
|
|
SPAの初期ロード時 |
|
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 Device 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();
});
Next.js / Reactの初期化
ステップ1でSDKスクリプトをロードした後、DOMの準備ができたら初期化します。初期化は、スクリプトのロード後にクライアントサイドで行う必要があります。
TypeScriptの型宣言
型宣言ファイルを作成して、Singular SDKのTypeScriptサポートを追加します:
// types/singular.d.ts
interface SingularConfig {
new (sdkKey: string, sdkSecret: string, productId: string): SingularConfig;
withCustomUserId(userId: string): SingularConfig;
withAutoPersistentSingularDeviceId(domain: string): SingularConfig;
withLogLevel(level: number): SingularConfig;
withSessionTimeoutInMinutes(timeout: number): SingularConfig;
withProductName(productName: string): SingularConfig;
withPersistentSingularDeviceId(singularDeviceId: string): SingularConfig;
withInitFinishedCallback(callback: (params: { singularDeviceId: string }) => void): SingularConfig;
}
interface SingularSDK {
init(config: SingularConfig): void;
event(eventName: string, attributes?: Record<string, any>): void;
conversionEvent(eventName: string): void;
revenue(eventName: string, currency: string, amount: number, attributes?: Record<string, any>): void;
login(userId: string): void;
logout(): void;
pageVisit(): void;
buildWebToAppLink(baseLink: string): string;
getSingularDeviceId(): string;
setGlobalProperties(key: string, value: string, override: boolean): void;
getGlobalProperties(): Record<string, string>;
clearGlobalProperties(): void;
}
interface Window {
singularSdk: SingularSDK;
SingularConfig: typeof SingularConfig;
}
Next.jsスクリプトコンポーネントによる基本的な初期化
ステップ1のScriptコンポーネントを使用する場合は、onLoadコールバックを追加してSDKを初期化します:
// pages/_app.tsx or app/layout.tsx
import Script from 'next/script'
export default function App({ Component, pageProps }) {
return (
<>
<Script
src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"
strategy="afterInteractive"
onLoad={() => {
// Initialize Singular SDK after script loads
const config = new (window as any).SingularConfig(
process.env.NEXT_PUBLIC_SINGULAR_SDK_KEY,
process.env.NEXT_PUBLIC_SINGULAR_SDK_SECRET,
process.env.NEXT_PUBLIC_SINGULAR_PRODUCT_ID
);
window.singularSdk.init(config);
}}
/>
<Component {...pageProps} />
</>
)
}
useEffectフックによる初期化
動的インポート メソッドを使用している場合や、より多くの制御が必要な場合は、useEffect を使用して初期化します:
// components/SingularInitializer.tsx
'use client'
import { useEffect } from 'react'
export default function SingularInitializer() {
useEffect(() => {
// Wait for script to load, then initialize
const checkAndInit = () => {
if (window.singularSdk && window.SingularConfig) {
const config = new window.SingularConfig(
process.env.NEXT_PUBLIC_SINGULAR_SDK_KEY!,
process.env.NEXT_PUBLIC_SINGULAR_SDK_SECRET!,
process.env.NEXT_PUBLIC_SINGULAR_PRODUCT_ID!
);
window.singularSdk.init(config);
} else {
// Retry if SDK not loaded yet
setTimeout(checkAndInit, 100);
}
};
checkAndInit();
}, []); // Empty dependency - run once on mount
return null;
}
設定オプションによる初期化
クロスサブドメイントラッキングやカスタムユーザーIDのような追加機能を有効にするには、.with メソッドをチェーンします:
const config = new window.SingularConfig(
process.env.NEXT_PUBLIC_SINGULAR_SDK_KEY!,
process.env.NEXT_PUBLIC_SINGULAR_SDK_SECRET!,
process.env.NEXT_PUBLIC_SINGULAR_PRODUCT_ID!
)
.withAutoPersistentSingularDeviceId('your-domain.com')
.withCustomUserId(userId)
.withLogLevel(2); // 0=None, 1=Warn, 2=Info, 3=Debug
window.singularSdk.init(config);
シングルページアプリケーション(SPA)ルーティング
クライアントサイドルーティングを使用するNext.jsアプリケーションでは、初期化とは別にルートの変更をトラッキングする必要があります。
重要:SDKの初期化は、最初のページロード時に一度だけ行います。その後、ルートが変更されるたびにwindow.singularSdk.pageVisit()を呼び出します。
// components/SingularPageTracker.tsx
'use client'
import { useEffect, useRef } from 'react'
import { usePathname } from 'next/navigation' // App Router
// or import { useRouter } from 'next/router' // Pages Router
export default function SingularPageTracker() {
const pathname = usePathname() // App Router
// const router = useRouter(); const pathname = router.pathname // Pages Router
const isInitialized = useRef(false);
// Initialize SDK once on mount
useEffect(() => {
if (!isInitialized.current && window.singularSdk && window.SingularConfig) {
const config = new window.SingularConfig(
process.env.NEXT_PUBLIC_SINGULAR_SDK_KEY!,
process.env.NEXT_PUBLIC_SINGULAR_SDK_SECRET!,
process.env.NEXT_PUBLIC_SINGULAR_PRODUCT_ID!
);
window.singularSdk.init(config);
isInitialized.current = true;
}
}, []); // Empty dependency - runs once
// Track page visits on route changes (NOT on initial mount)
useEffect(() => {
if (isInitialized.current && window.singularSdk) {
window.singularSdk.pageVisit();
}
}, [pathname]); // Runs when pathname changes
return null;
}
初期化コールバックの使用
SDKの準備完了後にコードを実行する必要がある場合(たとえば、Singular Device IDを取得する場合など)は、.withInitFinishedCallback() を使用します:
const config = new window.SingularConfig(
process.env.NEXT_PUBLIC_SINGULAR_SDK_KEY!,
process.env.NEXT_PUBLIC_SINGULAR_SDK_SECRET!,
process.env.NEXT_PUBLIC_SINGULAR_PRODUCT_ID!
)
.withInitFinishedCallback((initParams) => {
const singularDeviceId = initParams.singularDeviceId;
console.log('Singular Device ID:', singularDeviceId);
// Store for analytics or pass to other services
});
window.singularSdk.init(config);
Next.js初期化のベストプラクティス
- SDKの初期化は、アプリケーションのマウント時に一度だけ行います。
-
SDK認証情報には、
NEXT_PUBLIC_接頭辞を持つ環境変数を使用します。 -
SPAの場合は、ナビゲーションを追跡するために、ルート変更時に
window.singularSdk.pageVisit()。 -
useRefを使用して初期化状態を追跡し、重複初期化を防止します。 -
SSR中のエラーを防ぐために、メソッドを呼び出す前に
window.singularSdk。 - 開発モードでテストし、SDKがサーバー側のエラーなしに正しく初期化されることを確認する。
Next.jsの開発者チェックリスト
- ステップ1でスクリプトが正常にロードされたことを確認します。
- SDKの認証情報で環境変数を設定します。
- 開発者の利便性を向上させるために、TypeScriptの型宣言を作成します。
-
初期化メソッドを選択します:スクリプトコンポーネント
onLoadまたはuseEffectフック。 -
SPAの場合は、
pageVisit()を使用してルート変更トラッキングを実装する。 -
コンソールで
__PAGE_VISIT__イベントを確認し、初期化をテストする。
-
を実際のSDKキーに置き換えてください。
'sdkKey'を実際のSDKキーに置き換えてください。 -
実際のSDKキーに置き換えます。
'sdkSecret'を実際のSDKシークレットに置き換えます。 -
実際の製品IDに置き換えます。
'productId'を実際のプロダクトIDに置き換えます。com.website-nameのようになり、SingularプラットフォームのAppsページにあるBundleIDの値と一致する必要があります。
設定オプション
.with メソッドを連結して追加機能を有効にすることで、WebSDK のセットアップを強化できます。
たとえば、Singular Device ID (SDID) をCookieに保存してクロスサブドメイントラッキングをサポートしたり、アクティブなログイン状態のサイト訪問者のためにカスタムユーザ ID を含めることができます:
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)
|
自動クロスサブドメイントラッキングを有効にする | クッキーを使用した自動持続 |
|
|
手動でのクロスサブドメイントラッキングを有効にする | 単一デバイスIDを手動で設定する |
.withInitFinishedCallback(callback)
|
SDK初期化完了時にコールバックを呼び出す | 初期化完了時にコールバック関数を呼び出す |
開発者向けチェックリスト
- SDKの認証情報とプロダクトIDを集めます。
- カスタム設定(ユーザーID、タイムアウトなど)が必要かどうかを決定します。
- 上記の例を使って Singular Intitialization 関数と SingularConfig オブジェクトを作成します。
- 初期化がページロード時に一度だけトリガーされることを必ずテストしてください。
ヒント Organic Searchトラッキングのような高度な設定の場合、Singular SDKが初期化される前にクエリーパラメーターを調整するなどのカスタムJavaScriptを実装する必要があるかもしれません。 変更が正しく取り込まれるように、カスタムコードがSingular初期化関数の前に実行されるようにしてください。オーガニック検索トラッキングの実装方法の詳細はこちらをご覧ください。
ステップ3:イベントのトラッキング
SDKを初期化した後、ユーザーがウェブサイト上で重要なアクションを行ったときにカスタムイベントをトラッキングすることができます。
重要Singularは重複イベントをブロックしません!ページのリフレッシュや重複を防止するのは開発者の責任です。誤った収益データを防ぐために、収益イベント専用の重複排除方法を組み込むことをお勧めします。例については、下記の "ステップ5:重複イベントの防止 "を参照してください。
基本的なイベントトラッキング
シンプルなイベントをトラッキングするか、有効な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);
コンバージョンイベントのトラッキング
コンバージョンイベントをトラッキングします:
var eventName = 'sng_complete_registration';
window.singularSdk.conversionEvent(eventName);
収益イベントのトラッキング
収益情報を持つコンバージョンイベントをトラッキングし、より詳細なコンテキストのためにオプションの属性を追加します:
- 収益通貨を "USD"、"EUR"、"INR "などの3文字のISO 4217通貨コードとして渡します。
- 収益金額の値を10進数として渡す。
// Revenue event tracking
var revenueEventName = 'sng_ecommerce_purchase';
var revenueCurrency = 'USD';
var revenueAmount = 49.99;
window.singularSdk.revenue(revenueEventName, revenueCurrency, revenueAmount);
// Optional: With attributes for more context
var revenueEventName = 'sng_ecommerce_purchase';
var revenueCurrency = 'USD';
var revenueAmount = 49.99;
var attributes = {
key1: 'value1', // First custom attribute
key2: 'value2' // Second custom attribute
};
window.singularSdk.revenue(revenueEventName, revenueCurrency, revenueAmount, attributes);
一般的なイベント実装パターン
ページロードイベント
ページロードイベントトラッキングメカニズムは、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);
});
ボタン・クリック・イベント
ボタンクリックイベントトラッキングメカニズムでは、JavaScriptを使用して、ユーザーがIDcheckout-buttonのボタンをクリックしたことを監視し、分析トラッキングのためにSingular SDKを使用してcheckout_started イベントをトリガーします。このコードでは、clickイベントリスナーを使用してボタン操作を検出し、(data-event-fired 属性を使用して)複数のイベントトリガーを防止する仕組みを含み、カスタム属性(cart_value とcurrency )を持つイベントを Singular プラットフォームに送信します。これは、ユーザーがいつチェックアウトプロセスを開始したかを追跡するeコマース分析に理想的で、ページセッションごとに一度だけイベントを発生させることで正確なデータを確保します。
/**
* Tracks a checkout started event with the Singular SDK when a button is clicked.
* @param {string} eventName - The name of the event to track (e.g., 'checkout_started').
* @param {Object} attributes - A JSON object with custom event attributes.
* @param {number} attributes.cart_value - The total value of the cart (e.g., 99.99).
* @param {string} attributes.currency - The currency of the cart value (e.g., 'USD').
*/
document.getElementById('checkout-button').addEventListener('click', function(e) {
// Prevent multiple fires
if (this.hasAttribute('data-event-fired')) {
return;
}
this.setAttribute('data-event-fired', 'true');
var eventName = 'checkout_started';
var attributes = {
cart_value: 99.99, // Total value of the cart
currency: 'USD' // Currency of the cart value
};
window.singularSdk.event(eventName, attributes);
});
フォーム送信イベント
フォーム送信イベントトラッキングの仕組みでは、JavaScript を使用して、ユーザーが IDsignup-form を持つフォームを送信したことを監視し、分析トラッキングのために Singular SDK を使用してsignup_completed イベントをトリガーします。コードはsubmitイベントリスナーを使用してフォーム送信を検出し、(data-event-fired属性を使用して)複数のイベントトリガーを防止する仕組みを含み、カスタム属性(signup_method )を使用してイベントを Singular プラットフォームに送信します。これは、ユーザー獲得やサインアップ方法の監視など、分析ワークフローでユーザーのサインアップを追跡するのに理想的で、ページセッションごとに一度だけイベントを発生させることで正確なデータを確保します。
以下は、window.singularSdk.event を使用するように元のコードを変更したクリーンな例です。わかりやすくするために、イベント名と属性を分けています。
/**
* Tracks a signup completed event with the Singular SDK when a form is submitted.
* @param {string} eventName - The name of the event to track (e.g., 'signup_completed').
* @param {Object} attributes - A JSON object with custom event attributes.
* @param {string} attributes.signup_method - The method used for signup (e.g., 'email').
* @example
* // Track a signup event with additional attributes
* var eventName = 'signup_completed';
* var attributes = {
* signup_method: 'email',
* email: document.getElementById('email')?.value || 'unknown'
* };
* window.singularSdk.event(eventName, attributes);
*/
document.getElementById('signup-form').addEventListener('submit', function(e) {
// Prevent multiple fires
if (this.hasAttribute('data-event-fired')) {
return;
}
this.setAttribute('data-event-fired', 'true');
var eventName = 'signup_completed';
var attributes = {
signup_method: 'email' // Method used for signup
};
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が他者と共有することはありません。
- ユーザーIDの値は、Singular SDKメソッドで設定されると、logout() メソッドで設定が解除されるまで、またはブラウザのローカルストレージが削除されるまで保持されます。ウェブサイトを閉じたり更新しても、ユーザーIDは解除されません。
- プライベート/インコグニートモードでは、ブラウザが閉じられるとローカルストレージが自動的に削除されるため、SDKはユーザーIDを永続化できません。
ユーザIDを設定するには、login() メソッドを使用します。ユーザIDを解除するには(ユーザがアカウントを「ログアウト」した場合など)、logout() メソッドを呼び出します。
注:複数のユーザが1つのデバイスを使用する場合、ログインとログアウトのたびにユーザIDを設定および解除するログアウトフローを実装することを推奨します。
ウェブサイト上でSingular SDKが初期化されたときにすでにユーザーIDがわかっている場合は、設定オブジェクトにユーザーIDを設定します。こうすることで、Singularは最初のセッションからユーザーIDを持つことができます。しかし、ユーザーIDは通常、ユーザーが登録するかログインを実行するまで使用できません。その場合は、登録フローが完了した後にlogin() を呼び出してください。
ヒントモバイルSDKで使用するのと同じカスタマ・ユーザIDを使用してください。これにより、クロスデバイスのアトリビューションが可能になり、プラットフォーム間でのユーザー行動の完全なビューが提供されます。
カスタマー・ユーザーIDのベスト・プラクティス
- ユーザーがログインまたはサインアップすると同時に設定する。
- ウェブとモバイルのプラットフォームで同じIDを使用する。
- 個人を特定できる情報(電子メール、電話番号)を使用しない。
- 社内ユーザーIDまたはデータベースIDを使用する
- 数字であっても、文字列として送信する:
// After user logs in or signs up
var userId = 'user_12345';
window.singularSdk.login(userId);
// After user logs out
window.singularSdk.logout();
ステップ5:重複イベントの防止
重要です!これは最も一般的な実装ミスの1つです。適切な安全策を講じないと、イベントが複数回発生し、メトリクスが膨れ上がる可能性があります。
ウェブアプリケーションで収益イベントの重複を防ぐことは、正確な分析とコンバージョンの過剰報告を避けるために重要です。目標は、ユーザーがページを再読み込みしたり、不注意で複数の送信をトリガーした場合でも、各ユニークなイベントがセッションまたはページ訪問につき1回しか発生しないようにすることです。
- イベントの詳細とオプションのカスタム属性に基づいて)各イベントに固有のキーが生成されます。
- このキーはブラウザの sessionStorage(永続的な重複排除の場合は localStorage)に保存される。
- 送信する前に、コードは同じペイロードを持つ収益イベントがすでに発生したかどうかをチェックする。
- もしそうでなければ、イベントを送信し、キーを保存する。
- そうであれば、繰り返しをブロックし、ユーザーまたは開発者に通知する。
セッション保存メソッドの使用
// Sends a revenue event to Singular WebSDK, preventing duplicates in the same session
// @param {string} eventName - Name of the revenue event (defaults to "web_purchase")
// @param {number} amount - Revenue amount (defaults to 0)
// @param {string} currency - Currency code (defaults to "USD", normalized to uppercase)
// @param {object} [attributes] - Optional key-value pairs for additional event data
function sendRevenueEvent(eventName, amount, currency, attributes) {
// Normalize inputs: set defaults and ensure currency is uppercase
eventName = eventName ? eventName : "web_purchase";
currency = currency ? currency.toUpperCase() : "USD";
amount = amount ? amount : 0;
// Create a payload object to standardize event data
var payload = {
eventName: eventName,
amount: amount,
currency: currency,
attributes: attributes ? attributes : null // Null if no attributes provided
};
// Generate a unique key for sessionStorage by hashing the payload
// This ensures deduplication of identical events in the same session
var storageKey = 'singular_revenue_' + btoa(JSON.stringify(payload));
// Check if the event was already sent in this session to prevent duplicates
if (!sessionStorage.getItem(storageKey)) {
// Mark event as sent in sessionStorage
sessionStorage.setItem(storageKey, 'true');
// Send revenue event to Singular SDK, including attributes if provided and valid
if (attributes && typeof attributes === 'object' && Object.keys(attributes).length > 0) {
window.singularSdk.revenue(eventName, currency, amount, attributes);
} else {
// Fallback to basic revenue event without attributes
window.singularSdk.revenue(eventName, currency, amount);
}
// Log event details for debugging
console.log("Revenue event sent:", payload);
} else {
// Log and alert if a duplicate event is detected
console.log("Duplicate revenue event prevented:", payload);
alert("Duplicate revenue event prevented!");
}
}
ステップ6:実装のテスト
SDKを実装したら、ブラウザの開発者ツールを使用して、SDKが正しく動作することを確認します。
SDKのロードを確認する
- ブラウザでウェブサイトを開きます。
- デベロッパーツールを開く(F12または右クリック→Inspect)
- コンソールタブに移動します。
-
typeof singularSdkと入力し、Enterキーを押します。 - undefined "ではなく、"function "オブジェクトが表示されるはずです。
ネットワークリクエストの確認
- デベロッパーツールを開く(F12)
- ネットワークタブに移動する
- ページをリロードする
-
singularまたはsdk-apiでフィルターをかける -
sdk-api-v1.singular.netへのリクエストを探す - リクエストをクリックして詳細を見る
-
ステータスコードが
200であることを確認する - リクエストのペイロードにプロダクトIDが含まれていることを確認します。これは、ペイロードの "
i" パラメータにあります。
成功! sdk-api-v1.singular.net へのリクエストがステータスコード 200 で表示されたら、SDK は Singular に正常にデータを送信しています。
イベントの確認
- ウェブサイト上でイベントをトリガーします(ボタンのクリック、フォームの送信など)。
-
ネットワークタブで、
sdk-api-v1.singular.netへの新しいリクエストを探します。 - リクエストをクリックし、ペイロードまたはリクエストタブを表示します。
-
イベント名「
n」パラメータがリクエストに表示されていることを確認する。
エンドツーエンドのテストには、テストコンソールを使用します。
Singularダッシュボードで利用可能なSDKコンソールを使用して、Web SDKの統合をテストできます。
- Singularプラットフォームで、Developer Tools > Testing Consoleに進みます。
- Add Deviceをクリックします。
- プラットフォーム「Web」を選択し、SingularデバイスIDを入力します。
- ブラウザのペイロードからSingularデバイスIDを取得します。上記のスクリーンショットを確認し、イベント上の
SDIDを見つけてください。 - 別ウィンドウでテストしている間、テストコンソールはアクティブ状態のままでなければなりません。コンソールが閉じている間やオフラインの間は、トリガーされたイベントは表示されません。
- SDID を追加したら、Web ページをリロードしていくつかのイベントをトリガーすることができます。SDKコンソールには「__PAGE_VISIT__」やその他のイベント(トリガーされた場合)が表示されます。
- エクスポートログ(Reports & Insights Export Logs)を利用することも、テスト結果を検証する方法です。このデータセットは1時間遅延します。
ステップ7:Web-to-Appフォワーディングの実装
ウェブからアプリへのアトリビューション転送
Singular WebSDK を使用して、ウェブサイトからモバイルアプリへのユーザージャーニーを追跡し、モバイルアプリのインストールと再エンゲージメントへの正確なウェブキャンペーン帰属を可能にします。以下の手順に従って、デスクトップユーザー向けのQRコードサポートを含む、ウェブからアプリへの転送を設定してください。
- ウェブサイトからモバイルアプリへのアトリビューション転送ガイドに従って、モバイルウェブアトリビューション用にSingular WebSDKを設定してください。
- デスクトップ Web-to-App トラッキングの場合:
- 上記のガイドのセットアップを完了してください。
- Singular Mobile Web-to-App Link(例:
https://yourlink.sng.link/...)とWebSDK関数buildWebToAppLink(link)、QRCode.jsのようなダイナミックQRコードライブラリを使用します。 - リンクをエンコードしたQRコードをウェブページに生成します。デスクトップユーザーはモバイルデバイスでQRコードをスキャンしてアプリを開き、アトリビューション用のキャンペーンデータを渡すことができます。
- QRコード生成とウェブからアプリへのリンク処理の実例については、Singular WebSDKデモをご覧ください。
ヒントモバイルのアプリ内ブラウザのウェブビュー(Facebook、Instagram、TikTokで使用されているような)は、ユーザーがデバイスのネイティブブラウザに移動するとSingularデバイスIDが変更され、アトリビューションが中断される可能性があります。
これを防ぐには、各広告ネットワークで常に適切な Singular トラッキングリンク形式を使用してください:
Singular WebSDKデモ
下記はドキュメント化されたSingular WebSDK APIを使用したシンプルで包括的な実装です。このサンプルでは、カスタムイベント、コンバージョンイベント、収益イベント、そしてWebSDK がサポートする Web-to-App 転送を使用した Web-to-App リンクサポートを明確に分離しています。
このコードをローカルの HTML ファイルで実行したり、サイト内で変更して高度な統合やトラブルシューティングを行うことができます。
以下のコードをコピーして 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';
var config = new SingularConfig(sdkKey, sdkSecret, productId)
.withInitFinishedCallback(function(initParams) {
var singularDeviceId = initParams.singularDeviceId;
console.log('Singular Device ID:', singularDeviceId);
// Optionally store for use in events
// sessionStorage.setItem('singularDeviceId', singularDeviceId);
});
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 SDKでは、アプリから送信されるすべてのセッションやイベントと一緒にSingularサーバーに送信されるカスタムプロパティを定義できます。これらのプロパティは、ユーザーやアプリのモード/ステータスなど、どんな情報でも表すことができます。
-
有効なJSONオブジェクトとして、最大5つのグローバルプロパティを定義できます。グローバルプロパティは、クリアされるかブラウザのコンテキストが変更されるまで、ブラウザ
localstorageに保持されます。 -
各プロパティ名と値の長さは200文字までです。これより長いプロパティ名や値を渡すと、200文字に切り詰められます。
-
グローバルプロパティは現在Singularのユーザーレベルのイベントログ(属性ログのエクスポートを参照)とポストバックに反映されます。
-
グローバルプロパティはSingularからサードパーティへのポストバックで送信することができます。
WebSDKが初期化される前にグローバルプロパティの設定をサポートするには、カスタムsetGlobalPropertyBeforeInit関数を実装し、SDK初期化の前にこれを呼び出す必要があります。初期化前にグローバルプロパティを設定する必要がない場合は、このカスタムコードを省略できます。
初期化後にGlobal Properitesを処理するには、SDK関数を使用する必要があります:setGlobalProperties() getGlobalProperties() clearGlobalProperties() 。
/**
* Set a Singular global property before SDK initialization.
* Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
*
* @param {string} sdkKey - The Singular SDK key.
* @param {string} productId - The Singular product ID.
* @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.
*/
function setGlobalPropertyBeforeInit(sdkKey, productId, propertyKey, propertyValue, overrideExisting) {
const storageKey = `${sdkKey}_${productId}_global_properties`;
let properties = {};
// Try to load any existing properties
const existing = localStorage.getItem(storageKey);
if (existing) {
try {
properties = JSON.parse(existing) || {};
} catch (e) {
// If parsing fails, reset properties
properties = {};
}
}
// Only allow up to 5 keys
const propertyExists = Object.prototype.hasOwnProperty.call(properties, propertyKey);
const numKeys = Object.keys(properties).length;
if (!propertyExists && numKeys >= 5) {
console.warn("You can only set up to 5 Singular global properties.");
return;
}
// Apply logic for overwrite or skip
if (propertyExists && !overrideExisting) {
console.log(`Global property '${propertyKey}' exists and overrideExisting is false; property not changed.`);
return;
}
properties[propertyKey] = propertyValue;
localStorage.setItem(storageKey, JSON.stringify(properties));
console.log("Singular Global Properties set:", storageKey, properties);
}
/** * SDK初期化前にSingularグローバルプロパティを設定します。 * 最大5つのキー/値のペアを許可します。オプションで、キーに対して既存の値を上書きします。 * @param {string} propertyKey - 設定するプロパティキー。 * @param {string} propertyValue - 設定するプロパティ値。
* @param {boolean} overrideExisting - プロパティが既に存在する場合に上書きするかどうか */ // 使用法 window.singularSdk.setGlobalProperties(propertyKey, propertyValue, overrideExisting);
// Get the JSON Object of global property values.
// Usage
window.singularSdk.getGlobalProperties();
// Clears all global property values.
// Usage
window.singularSdk.clearGlobalProperties();
オーガニック検索トラッキング
重要この例は、Organic Searchトラッキングを有効にするための回避策として提供されています。このコードはあくまでも例として使用し、マーケティング部門のニーズに基づいてウェブ開発者が更新および維持する必要があります。 Organic Searchトラッキングは広告主ごとに異なる意味を持つ場合があります。 サンプルを確認し、ニーズに合わせて調整してください。
これを使用する理由
-
キャンペーンパラメータが存在しない場合でも、オーガニック検索の訪問が適切にトラッキングされるようにします。
-
Singular「Source」パラメータ
wpsrc(リファラー)の値と「Campaign Name」パラメータwpcn「OrganicSearch」としてURLに追加し、明確なアトリビューションを実現します。 -
後で使用できるように、現在のURLとリファラーを
localStorage。 -
純粋なJavaScriptで、依存性はゼロ。
仕組み
-
ページのURLにGoogle、Facebook、TikTok、UTMなどのキャンペーンパラメータがあるかチェックします。
-
キャンペーンパラメータが存在せず、リファラーが検索エンジンの場合、:
-
wpsrc(リファラーを値として) -
wpcn(OrganicSearchを値として)
-
-
ページをリロードすることなく、ブラウザのURLを更新します。
-
現在のURLとリファラーを
sng_urlとsng_refとしてlocalStorageに保存します。
使用方法
-
setupOrganicSearchTracking.jsをライブラリとしてサイトに追加します。 -
WebSDK を初期化する前に
setupOrganicSearchTracking()関数を呼び出します。
// 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を設定する
Singular SDKにデバイスIDを自動的に保持させたくない場合は、例えばトップレベルドメインCookieやサーバーサイドCookieを使って、ドメイン間でIDを手動で保持することができます。値はSingularが以前に生成したIDで、有効なuuid4形式でなければなりません。
注:Singular Device IDは、initメソッドを呼び出した後、またはInitFinishedCallbackを使用して、singularSdk.getSingularDeviceId()を使用して読み取ることができます。
| withPersistentSingularDeviceId メソッド | |
|---|---|
|
説明 |
永続化したいシンギュラー・デバイス ID を含む構成オプションで SDK を初期化します。 |
| シグネチャ | withPersistentSingularDeviceId(singularDeviceId) |
| 使用例 |
|
次のステップ
- ウェブキャンペーン用のウェブサイトリンクをSingularで作成する
- モバイルインベントリ向けのウェブキャンペーンを実施している場合は、Google Ads Web、Facebook Web、TikTok Ads Webのガイドに従ってください。
- Singularレポートでデータを監視する