概要
INFO:Web Attributionはエンタープライズ向け機能です。アカウントでこの機能を有効にするには、カスタマーサクセスマネージャーにお問い合わせください。
このガイドでは、ネイティブ JavaScript を使用した Singular WebSDK の実装手順を説明します。この方法は最も信頼性の高いトラッキングを提供し、一般的な広告ブロッカーによってブロックされないため、ほとんどの実装で推奨されます。
重要!
- ネイティブ JavaScript と Google タグマネージャーの両方を実装しないでください。重複トラッキングを避けるため、いずれか一方のみを選択してください。
- Singular WebSDKはユーザーのブラウザでクライアントサイドに実行されるように設計されています。正常に機能するには、localStorageや Document Object Model(DOM)などのブラウザ機能へのアクセスが必要です。サーバーサイド(例:Next.js SSRやnode.js経由)でSDKを実行しようとしないでください。サーバー環境ではブラウザAPIへのアクセスが提供されないため、トラッキングが失敗します。
前提条件
開始前に、以下が用意されていることを確認してください:
-
SDKキーとSDKシークレット:
- 確認方法: Singularアカウントにログインし、[Developer Tools] > [SDK Integration] > [SDK Keys] に移動してください。
-
プロダクトID:
-
概要:ウェブサイト固有の名前。逆DNS形式(例:
com.website-name)での使用が推奨されます。 - 重要性:このIDはウェブサイトをSingular内のアプリとして関連付け、Singularのアプリページに記載されているWebアプリのbundleIDと一致させる必要があります。
-
概要:ウェブサイト固有の名前。逆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ライブラリのパスで特定のバージョンを指定します。 例:1.4.8 は以下のように記述します:
<script src="https://web-sdk-cdn.singular.net/singular-sdk/1.4.8/singular-sdk.js"></script>
-
プロジェクトのルートディレクトリで
npm i singular-sdkを実行するか、 package.jsonファイルの dependencies セクションに"singular-sdk": "^1.4.8"を追加し、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、cookies) を必要とするため、 クライアントサイドでのみ読み込む必要があります。
重要:Singular SDK をサーバーサイドコード(例:getServerSideProps 、React Server Components、Node.js 環境)でロードしないでください。サーバーではブラウザ API が利用できないため、エラーが発生します。
方法 1: Next.js Script コンポーネントの使用(推奨)
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
}
環境変数の設定
TIP!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の初期化時に使用できます。
アプリルーター vs ページルーター
| ルーターの種類 | 主な違い | スクリプトの場所 |
|---|---|---|
| アプリルーター(Next.js 13+) |
コンポーネントはデフォルトでサーバーサイドコンポーネントです。ブラウザAPIを使用するには'use client' ディレクティブを追加します。
|
app/layout.tsx
|
| Pages Router(Next.js 12 以前) | デフォルトではすべてのコンポーネントがクライアントコンポーネントです。 |
pages/_app.tsx
|
ステップ 2: SDK の初期化
- ブラウザでページが読み込まれるたびに、必ず SDK を初期化してください。
- Singular のアトリビューションおよびイベントトラッキング機能を利用するには、初期化が必要です。
- 初期化により
__PAGE_VISIT__イベントをトリガーします。 -
__PAGE_VISIT__イベントは、以下の条件が満たされた際にサーバーサイドで新しいセッションを生成するために使用されます:- ユーザーがURLに新しい広告データ(UTMパラメータやWPパラメータなど)を伴って到着した場合、または
- 前回のセッションが期限切れになった場合(30分間の非アクティブ状態後)。
- セッションはユーザー維持率の測定と再エンゲージメントアトリビューションの支援に使用されます。
- 初期化関数を作成し、ページ読み込み後のDOM Ready時にこの関数を呼び出してください。
- 他のSingularイベントが報告される前に初期化が行われるようにしてください。
-
シングルページアプリケーション(SPA)の場合、最初のページロード時にSingular SDKを初期化し、その後、新しいページビューを表すルート変更のたびにSingularPage Visit関数
window.singularSdk.pageVisit()を呼び出してください。
基本的な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)ルーティングを用いた基本初期化
| シナリオ | 対応方法 |
|---|---|
|
初回ページロード時 |
|
|
新しいルート/ページへの遷移 |
|
|
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
デバイス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コンポーネントを使用する場合、SDKを初期化するためにonLoadコールバックを追加します:
// 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;
}
設定オプションを使用した初期化
.with メソッドをチェーンして、クロスサブドメイン追跡やカスタムユーザーIDなどの追加機能を有効にします:
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デバイス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を使用する。 -
サーバーサイドレンダリング時のエラー防止のため、
メソッド呼び出し前に
window.singularSdkのNULLチェックを追加してください。 - 開発モードでテストし、サーバーサイドエラーなしでSDKが正しく初期化されることを確認してください。
Next.js 開発者チェックリスト
- ステップ1でスクリプトが正常に読み込まれたことを確認してください。
- SDK 認証情報で環境変数を設定してください。
- 開発者エクスペリエンス向上のため、TypeScript型宣言を作成する。 初期化メソッドを選択: スクリプトコンポーネント `xml-ph-0000@deepl.internal` またはフック `xml-ph-0001@deepl.internal`
-
初期化方法を選択:スクリプトコンポーネント
onLoadまたはuseEffectフック。 -
SPAの場合、
pageVisit()でルート変更の追跡を実装してください。 -
コンソールで
__PAGE_VISIT__イベントを確認し、 初期化をテストします。
-
実際のSDKキーで
'sdkKey'を実際のSDKキーに置き換えてください。 -
を
'sdkSecret'を実際のSDKシークレットに置き換えてください。 -
Replace
'productId'実際のプロダクトIDに置き換えてください。例:com.website-name。Singularプラットフォームの「Apps」ページにあるBundleIDの値と一致している必要があります。
設定オプション
.with のメソッドをチェーンして追加機能を有効にし、WebSDK の設定を強化します。
例:- クッキーにSingularデバイスID(SDID)を永続化してクロスサブドメイン追跡をサポートする- ログイン状態が有効なリピーター訪問者向けにカスタムユーザー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)
|
自動クロスサブドメイン追跡を有効化 | Cookieを使用した自動永続化 |
|
|
手動クロスサブドメイン追跡を有効にする | 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:重複イベントの防止」を参照してください。
基本イベントのトラッキング
単純なイベントを追跡するか、有効な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);
収益イベント追跡
収益情報を含むコンバージョンイベントを追跡し、 より詳細なコンテキストを得るためにオプションの属性を追加します:
- 収益通貨は3文字のISO 4217通貨コード(例: 「USD」、「EUR」、または「INR」)で指定してください。
- 収益金額は小数点値で渡します。
// 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を使用して
IDがcheckout-buttonのボタンがクリックされたタイミングを監視し、
Singular SDKを利用したcheckout_started イベントをトリガーして
分析追跡を行います。 コードはclickイベントリスナーでボタン操作を検知し、data-event-fired 属性による多重イベント発生防止機構を組み込み、
カスタム属性(cart_value とcurrency )を付加したイベントを
Singularプラットフォームへ送信します。これはEC分析において
ユーザーがチェックアウトプロセスを開始したタイミングを追跡するのに理想的であり、
ページセッションごとにイベントを1回のみ発火させることで正確なデータを保証します。
/**
* 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プラットフォームへイベントを送信します。
ユーザー獲得や登録方法の監視など、
アナリティクスワークフローにおけるユーザー登録追跡に最適です。
ページセッションごとにイベントを1回のみ発火させることで
正確なデータを保証します。
以下は、元のコードを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値は、全プラットフォーム(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を使用する
- 数値であっても文字列として送信する:
// After user logs in or signs up
var userId = 'user_12345';
window.singularSdk.login(userId);
// After user logs out
window.singularSdk.logout();
ステップ5: イベント重複排除(任意)
重要!サイトが短時間内に同一トリガーを複数回発火させる場合、重複したイベントがエクスポートされる可能性があります。SDKの重複排除機能を有効にすると、自動的に重複が抑制されます。
SingularのWeb SDKは、短時間内に繰り返しトリガーされたことによる重複エクスポートを減らすためのオプションのイベント重複排除をサポートしています。有効にすると、SDKは設定された時間枠内で同じ重複排除パラメータに一致する重複イベントを削除します。
有効化方法
-
withEventsDedupEnabled: イベント重複排除を有効化(デフォルトは無効)。 -
withTimeBetweenEvents: 2つのイベントを重複と見なす最大時間枠(ミリ秒単位)。デフォルトは1000ms(1秒)。
JavaScript
// Enable optional event deduplication (recommended when triggers may fire repeatedly)
var config = new SingularConfig("SDK_KEY", "SDK_SECRET", "PRODUCT_ID")
.withEventsDedupEnabled() // turns deduplication on
.withTimeBetweenEvents(1000); // dedup window in ms (default: 1000 = 1s)
window.singularSdk.init(config);
重複の検出方法
重複排除が有効な場合、SDKはイベントの主要フィールドからハッシュ値を計算し、時間ウィンドウ内の重複を抑制します。重複排除パラメータには以下が含まれます:EventName 、EventProductName 、IsRevenueEvent 、CustomUserId 、GlobalProperties 、MatchId 、WebUrl (またSDKはハッシュ化時にrevenue/argsなどの追加イベント「extra」ペイロードも考慮します)。
ステップ6: 実装のテスト
SDKを実装した後、ブラウザの開発者ツールを使用して正常に動作していることを確認してください。
SDKの読み込み確認
- ブラウザでウェブサイトを開く
- 開発者ツールを開く(F12 または右クリック → [要素を検査])
- コンソールタブに移動
-
typeof singularSdkと入力しEnterキーを押す - 「undefined」ではなく「function」オブジェクトが表示されるはずです
ネットワークリクエストを確認する
- 開発者ツールを開く(F12)
- ネットワークタブに移動
- ページを再読み込み
-
singularまたはsdk-apiでフィルタリング -
sdk-api-v1.singular.netへのリクエストを探す - リクエストをクリックして詳細を表示
-
ステータスコードが
200であることを確認 - リクエストペイロードにプロダクトIDが含まれていることを確認してください。これはペイロードの「
i」パラメータ内にあります。
成功!ステータスコード 200 のsdk-api-v1.singular.net へのリクエストが表示された場合、SDK は Singular へデータを正常に送信しています。
イベントの確認
- ウェブサイト上でイベントをトリガーしてください(ボタンクリック、フォーム送信など)
-
ネットワークタブで、
sdk-api-v1.singular.netへの新規リクエストを確認してください - リクエストをクリックし、ペイロードまたはリクエストタブを表示
-
リクエスト内にイベント名「
n」パラメータが表示されていることを確認
エンドツーエンドテストにはテストコンソールを使用
SingularダッシュボードのSDKコンソールでWeb SDK統合をテストできます。
- Singularプラットフォームで、[Developer Tools] > [Testing Console] に移動します。
- 「デバイスを追加」をクリックします。
- プラットフォーム「Web」を選択し、SingularデバイスIDを入力します。
- SingularデバイスIDはブラウザのペイロードから取得します。イベント内の
SDIDの位置については、上記のスクリーンショットを参照してください。 - テスト中はコンソールをアクティブ状態に保ち、別ウィンドウでテストを実行してください。コンソールが閉じられているかオフライン状態の場合、トリガーされたイベントは表示されません。
- SDIDを追加後、ウェブページを再読み込みしイベントをトリガーしてください。「__PAGE_VISIT__」およびその他のイベント(トリガーされた場合)がSDKコンソールに表示されます。
- テスト結果を確認する別の方法として、エクスポートログ(レポート&インサイトエクスポートログ)の利用があります。このデータセットは1時間遅延します。
ステップ7: Web-to-Appフォワーディングの実装
Web-to-Appアトリビューション転送
Singular WebSDKを使用して、ウェブサイトからモバイルアプリへのユーザージャーニーを追跡し、ウェブキャンペーンのモバイルアプリインストールおよび再エンゲージメントへの正確なアトリビューションを実現します。デスクトップユーザー向けのQRコードサポートを含む、Web-to-Appフォワーディングの設定手順に従ってください。
- モバイルウェブアトリビューション用にSingular WebSDKを設定するには、当社の「ウェブサイトからモバイルアプリへのアトリビューション転送ガイド」に従ってください。
- デスクトップ向け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ファイルで実行するか、高度な統合やトラブルシューティングのためにサイト内で修正できます。
以下のコードを 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 SDK を使用すると、アプリから送信されるすべてのセッションおよびイベントとともに Singular サーバーに送信されるカスタムプロパティを定義できます。これらのプロパティは、ユーザー、アプリのモード/ステータス、その他任意の情報について、必要なあらゆる情報を表すことができます。
-
有効なJSONオブジェクトとして最大5つのグローバルプロパティを定義できます。 グローバルプロパティは、ブラウザの
localstorageに クリアされるかブラウザコンテキストが変更されるまで永続化されます。 -
各プロパティ名と値は最大200文字までです。 これより長いプロパティ名や値を渡した場合、 200文字に切り詰められます。
-
グローバルプロパティは現在、Singularの ユーザーレベルイベントログ(アトリビューションログのエクスポートを参照) およびポストバックに反映されます。
-
グローバルプロパティは、マッチング目的で Singularからサードパーティへ送信されるポストバックにおいて 利用可能です。
WebSDK初期化時にグローバルプロパティの設定をサポートするには、
configオブジェクトに.withGlobalProperties()オプションを実装する必要があります。
初期化後のグローバルプロパティを処理するには、
SDK関数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);
/**
* 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();
オーガニック検索トラッキング
重要!この例は、 ワークアラウンド ソリューションとして提供され、オーガニック検索トラッキングを有効にするためのものです。コードは あくまで 例として使用し、マーケティング部門のニーズに基づいてウェブ開発者が更新・保守を行う必要があります。 オーガニック 検索トラッキングは広告主ごとに異なる意味を持つ場合があります。 サンプルを 確認し、必要に応じて調整してください。
なぜこれを使うのか?
-
キャンペーンパラメータが存在しない場合でも、 オーガニック検索からの訪問が適切に追跡されることを保証します。
-
明確なアトリビューションのため、 URLに単数形「Source」パラメータ
wpsrc(リファラーからの値)と「Campaign Name」パラメータwpcn(「OrganicSearch」として)を追加します。 -
現在のURLとリファラーを
localStorageに保存し、 後で使用できるようにします。 -
純粋なJavaScript、依存関係ゼロ、統合が容易。
動作原理
-
ページURLから既知のキャンペーンパラメータ(Google、 Facebook、TikTok、UTMなど)をチェック。
-
キャンペーンパラメータが存在せず、リファラーが 検索エンジンの場合、以下を追加:
-
wpsrc(リファラーを値として) -
wpcn(値として OrganicSearch を設定)
-
-
ページを再読み込みせずにブラウザのURLを更新します。
-
現在のURLとリファラーを
localStorageにsng_urlおよびsng_refとして保存。
使用方法
-
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デバイスIDを生成し、ブラウザストレージを使用して永続化します。このストレージはサブドメイン間で共有できないため、SDKは各サブドメインごとに新しいIDを生成します。
サブドメイン間でSingularデバイスIDを永続化したい場合は、以下のいずれかの方法を使用できます:
方法 B(上級者向け):Singular デバイス ID の手動設定
Singular SDKによるデバイスIDの自動永続化を望まない場合、 ドメインを跨いで手動でIDを永続化できます。 例:トップレベルドメインの クッキーやサーバーサイドクッキーを使用。 値はSingularが事前に生成した 有効なuuid4形式のIDである必要があります。
注記:SingularデバイスIDは、 initメソッド呼び出し後またはInitFinishedCallback使用時に singularSdk.getSingularDeviceId()で取得可能です。
| withPersistentSingularDeviceId メソッド | |
|---|---|
|
説明 |
設定オプション(永続化したいSingularデバイスIDを含む)でSDKを初期化します。 |
| シグネチャ | withPersistentSingularDeviceId(singularDeviceId) |
| 使用例 |
|
次のステップ
- ウェブキャンペーン用にSingularでウェブサイトリンクを作成
- モバイル在庫向けのウェブキャンペーンを実行する場合は、Google Ads Web、Facebook Web、TikTok Ads Webのガイドに従ってください。
- Singularレポートでデータを監視