Overview
INFO: Web Attribution is an enterprise feature. Contact your Customer Success Manager to enable this feature for your account.
This guide walks through implementing the Singular WebSDK using native JavaScript. This method provides the most reliable tracking and is recommended for most implementations because it is not blocked by common ad blockers.
IMPORTANT!
- Do not implement both Native JavaScript and Google Tag Manager methods. Choose only one to avoid duplicate tracking.
- The Singular WebSDK is designed to run client-side in a user's browser. It requires access to browser features such as localStorage and the Document Object Model (DOM) to function correctly. Do not attempt to run the SDK server-side (for example, via Next.js SSR or node.js)—this will cause tracking failures, as server environments do not provide access to browser APIs.
Prerequisites
Before starting, ensure you have:
-
SDK Key & SDK Secret:
- Where to find them: Log into your Singular account and navigate to Developer Tools > SDK Integration > SDK Keys.
-
Product ID:
-
What it is: A unique name for your website, ideally using reverse DNS format (e.g.,
com.website-name). - Why it matters: This ID associates the website as an App in Singular and must match the Web App bundleID listed on your Apps Page in Singular.
-
What it is: A unique name for your website, ideally using reverse DNS format (e.g.,
- Permission to edit your website's HTML code.
- Access to add JavaScript in the
<head>section of your pages. - A list of events you want to track. View our Singular Standard Events: Full List and Recommended Events by Vertical for ideas.
Implementation Steps
Step 1: Add the SDK Library Script
Add the following code snippet to the <head> section of every page on your website. Place it as early as possible, ideally near the top of the <head> tag.
TIP! Adding the script early ensures the Singular JavaScript Library is available for any Singular functions in your page source code.
<script src="https://web-sdk-cdn.singular.net/singular-sdk/latest/singular-sdk.js"></script>
Specify the specific version in the path of the WebSDK JavaScript library, for example: 1.4.8 is noted here:
<script src="https://web-sdk-cdn.singular.net/singular-sdk/1.4.8/singular-sdk.js"></script>
-
Run
npm i singular-sdkin your project’s root directory, or add"singular-sdk": "^1.4.8"to the dependencies section in your package.json file and then runnpm install. -
Add the following code in the scripts you want to use the SDK.
import {singularSdk, SingularConfig} from "singular-sdk";
Using with Next.js / React
Next.js is a React framework that provides server-side rendering (SSR), static site generation (SSG), and React Server Components. Since the Singular WebSDK requires browser APIs (DOM, localStorage, cookies), you must load it client-side only.
IMPORTANT: Never load the Singular SDK in server-side
code (e.g., getServerSideProps, React Server Components,
or Node.js environments). This will cause errors because browser
APIs are not available on the server.
Method 1: Using Next.js Script Component (Recommended)
The <Script> component from Next.js provides optimized
loading strategies and prevents duplicate script injection across
route changes.
// 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} />
</>
)
}
Script Strategy Options:
-
afterInteractive(recommended): Loads after the page becomes interactive - ideal for analytics and tracking. -
lazyOnload: Loads during browser idle time - use for non-critical widgets.
Method 2: Dynamic Import for Component-Level Loading
For component-level control, use Next.js dynamic imports with
ssr: false to load the script only when needed:
// 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
}
Environment Variables Setup
TIP! Store your Singular credentials in environment
variables prefixed with NEXT_PUBLIC_ to make them
available in the browser:
# .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
These environment variables will be accessible in your client-side code and can be used during initialization in Step 2.
App Router vs Pages Router
| Router Type | Key Differences | Script Location |
|---|---|---|
| App Router (Next.js 13+) |
Components are server components by default. Add
'use client' directive for browser APIs.
|
app/layout.tsx
|
| Pages Router (Next.js 12 and earlier) | All components are client components by default. |
pages/_app.tsx
|
Step 2: Initialize the SDK
- Always initialize the SDK each time a page loads in the browser.
- Initialization is required for all Singular attribution and event tracking features.
- Initialization triggers a
__PAGE_VISIT__event. - The
__PAGE_VISIT__event is used to generate a new Session servers-side when the following conditions are met:- The user arrives with new advertising data in the URL (such as UTM or WP parameters), or
- The previous session has expired (after 30 minutes of inactivity).
- Sessions are used to measure user retention and support re-engagement attribution.
- Create an Initialization function and call this function on DOM Ready after page load.
- Ensure initialization occurs before any other Singular events are reported.
- For Single-Page Applications (SPA), initialize the Singular SDK on first page load and then call the Singular Page Visit function
window.singularSdk.pageVisit()on every route change that represents a new page view.
Basic DOM-Ready Initialization
Add an Event Listener to call the initSingularSDK()
on 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();
});
Basic Initialization with Global Properties
Due to the Singular SDK not being initialized, you must implement
the custom function to set the Global Properties in the
localstorage of the browser.
See the custom function setGlobalPropertyBeforeInit().
Once implemented you can set properties as outlined below before
the initialization of the 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();
});
Basic Initialization with Single-Page Application (SPA) Routing
| Scenario | What to Do |
|---|---|
|
First page load |
Call |
|
Navigating to a new route/page |
Call |
|
On initial load in SPA |
Do not call |
Example for SPAs (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;
Basic Initialization Using an Initialization Callback
If you need to run code after the SDK is ready (for example, to get
the Singular
Device ID), set a callback with .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 Initialization
After loading the SDK script in Step 1, initialize it when the DOM is ready. The initialization must occur client-side after the script has loaded.
TypeScript Type Declarations
Create a type declaration file to add TypeScript support for the Singular SDK:
// 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;
}
Basic Initialization with Next.js Script Component
When using the Script component from Step 1, add the onLoad
callback to initialize the 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} />
</>
)
}
Initialization with useEffect Hook
If you're using the dynamic import method or need more control, initialize
using 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;
}
Initialization with Configuration Options
Chain .with methods to enable additional features like
cross-subdomain tracking or custom user IDs:
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);
Single-Page Application (SPA) Routing
For Next.js applications with client-side routing, you need to track route changes separately from the initial initialization.
IMPORTANT: Initialize the SDK only once on first
page load. Then call window.singularSdk.pageVisit()
on every subsequent route change.
// 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;
}
Using an Initialization Callback
If you need to run code after the SDK is ready (for example, to get
the Singular Device ID), use .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);
Best Practices for Next.js Initialization
- Initialize the SDK only once on application mount, not on every route change.
-
Use environment variables with the
NEXT_PUBLIC_prefix for SDK credentials. -
For SPAs, call
window.singularSdk.pageVisit()on route changes to track navigation. -
Use
useRefto track initialization state and prevent duplicate initialization. -
Add null checks for
window.singularSdkbefore calling methods to prevent errors during SSR. - Test in development mode to ensure the SDK initializes correctly without server-side errors.
Developer Checklist for Next.js
- Verify the script loaded successfully in Step 1.
- Set up environment variables with your SDK credentials.
- Create TypeScript type declarations for better developer experience.
-
Choose initialization method: Script component
onLoadoruseEffecthook. -
For SPAs, implement route change tracking with
pageVisit(). -
Test initialization by checking the console for the
__PAGE_VISIT__event.
-
Replace
'sdkKey'with your actual SDK Key. -
Replace
'sdkSecret'with your actual SDK Secret. -
Replace
'productId'with your actual Product ID. It should look like:com.website-nameand it should match the BundleID value on the Apps page in the Singular platform.
Configuration Options
Enhance the WebSDK setup by chaining .with methods to enable extra features.
For example, to support cross-subdomain tracking by persisting the Singular Device ID (SDID) in a Cookie or to include a custom user ID for a returning site visitor with an active logged-in state:
var domain = 'website-name.com';
var config = new SingularConfig('sdkKey','sdkSecret','productId')
.withAutoPersistentSingularDeviceId(domain)
.withCustomUserId(userId);
SingularConfig Method Reference
Here are all the available ".with" methods.
| Method | Description | Learn More |
.withCustomUserId(customId)
|
Send the user ID to Singular | Setting the User ID |
.withProductName(productName)
|
An optional display name for the product | |
.withLogLevel(logLevel)
|
Configure the logging level: 0 - None (Default); 1 - Warn; 2 - Info; 3 - Debug. | |
.withSessionTimeoutInMinutes(timeout)
|
Set the session timeout in minutes (default: 30 minutes) |
|
.withAutoPersistentSingularDeviceId(domain)
|
Enable automatic cross-subdomain tracking | Auto-Persist using Cookies |
|
|
Enable manual cross-subdomain tracking | Set Singular Device ID Manually |
.withInitFinishedCallback(callback)
|
Invoke a callback when SDK initialization is complete | Invoking a Callback Function when Initialization is Complete |
.withGlobalProperties(globalProperties, false)
|
Set global properties during initialization; second parameter
is overrideExisting (boolean).
Use false to merge with existing properties,
true to overwrite existing properties.
|
Global Properties |
.withEventsDedupEnabled()
|
Enable optional event deduplication to reduce duplicate event exports (for example, repeated triggers fired within a short window) | Event Deduplication |
.withTimeBetweenEvents(timeBetweenEvents)
|
Set the maximum time window (ms) for deduplication (default: 1000ms / 1 second) | Event Deduplication |
Developer Checklist
- Gather your SDK credentials and Product ID.
- Decide if you need any custom settings (user ID, timeout, etc.).
- Construct a Singular Intitialization function and SingularConfig object using the examples above.
- Always test to make sure the initialization is only triggered once on the page load.
Step 3: Tracking Events
After initializing the SDK, you can track custom events when users perform important actions on your website.
IMPORTANT! Singular does not block duplicate events! It is the developers responsibility to add protections against page refreshes or duplication. It is recommended to incorporate some deduplication method specificlly for revenue events to prevent erroneous revenue data. See "Step 5: Preventing Duplicate Events" below for an example..
Basic Event Tracking
Track a simple event or add custom attributes using valid JSON to provide more context about the event:
// 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);
Conversion Event Tracking
Track a conversion event:
var eventName = 'sng_complete_registration';
window.singularSdk.conversionEvent(eventName);
Revenue Event Tracking
Track conversion events with revenue information and add optional attributes for more context:
- Pass revenue currency as a three-letter ISO 4217 currency code, such as “USD”, “EUR”, or “INR".
- Pass the revenue amount value as a decimal value.
// 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);
Common Event Implementation Patterns
Page Load Events
The page load event tracking mechanism uses JavaScript to monitor
when a webpage fully loads and then triggers an analytics event using
the Singular SDK. Specifically, it leverages the
window.addEventListener('load', ...) method to detect
when all page resources (e.g., HTML, images, scripts) have loaded.
Upon this event, the Singular SDK's event function is
called to log a custom event with associated attributes, enabling
tracking of user interactions for analytics purposes, such as monitoring
page views or user actions like registrations. The mechanism is commonly
used in web analytics to capture data about user behavior, such as
page visits or specific actions, with customizable attributes for
detailed insights.
The mechanism can be adapted to track a specific event (e.g., a
page_view event) with custom attributes, as shown
below. The code structure remains clean and modular, with the event
name and attributes defined separately for clarity and maintainability.
/**
* 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);
});
Button Click Events
The button click event tracking mechanism uses JavaScript to monitor
when a user clicks a button with the ID checkout-button
and triggers a checkout_started event using the Singular
SDK for analytics tracking. The code employs a click
event listener to detect the button interaction, includes a mechanism
to prevent multiple event triggers (using a
data-event-fired attribute), and sends the event with
custom attributes (cart_value and currency)
to the Singular platform. This is ideal for e-commerce analytics
to track when users initiate a checkout process, ensuring accurate
data by firing the event only once per page session.
/**
* 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);
});
Form Submission Events
The form submission event tracking mechanism uses JavaScript to monitor
when a user submits a form with the ID signup-form and
triggers a signup_completed event using the Singular
SDK for analytics tracking. The code employs a submit
event listener to detect form submission, includes a mechanism to
prevent multiple event triggers (using a data-event-fired
attribute), and sends the event with a custom attribute (signup_method)
to the Singular platform. This is ideal for tracking user signups
in analytics workflows, such as monitoring user acquisition or signup
methods, ensuring accurate data by firing the event only once per
page session.
Below is a clean example adapting the original code to use
window.singularSdk.event, separating the event name
and attributes for clarity.
/**
* 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);
});
Step 4: Setting Customer User ID
You may send your internal User ID to Singular using a Singular SDK method.
NOTE: If you use Singular's Cross-Device solution, you must collect the User ID across all platforms.
- The User ID can be any identifier and should not expose PII (Personally Identifiable Information). For example, you should not use a User's email address, username, or phone number. Singular recommends using a hashed value unique only to your first-party data.
- The User ID value passed to Singular should also be the same internal User ID you capture across all platforms (Web/Mobile/PC/Console/Offline).
- Singular will include the User ID in user-level exports, ETL, and Internal BI postbacks (if configured). The User ID is first-party data, and Singular does not share it with other parties.
- The User ID value, when set with the Singular SDK Method, will persist until it is unset using the logout() method or until the browser local storage is deleted. Closing or refreshing the website does not unset the User ID.
- In private/incognito mode, the SDK cannot persist the user ID because the browser automatically deletes the local storage when it is closed.
To set the User ID, use the login() method.
To
unset it (for example, if the User "logs out" of the account), call
logout() method.
NOTE: If multiple Users use a single device, we recommend implementing a logout flow to set and unset the User ID for each login and logout.
If you already know the user ID when the Singular SDK is initialized on the
website,
set the User ID in the config object. This way, Singular can have the User
ID
from the first Session. However, the User ID is typically unavailable until
the
User registers or performs a login. In that case, call
login() after the registration flow is complete.
TIP! Use the same Customer User ID that you use in your mobile SDKs. This enables cross-device attribution and provides a complete view of user behavior across platforms.
Best practices for Customer User ID:
- Set it as soon as the user logs in or signs up
- Use the same ID across web and mobile platforms
- Do not use personally identifiable information (email, phone number)
- Use your internal user ID or database ID
- Send as a string, even if it's numeric:
// After user logs in or signs up
var userId = 'user_12345';
window.singularSdk.login(userId);
// After user logs out
window.singularSdk.logout();
Step 5: Event Deduplication (Optional)
IMPORTANT! If your site can fire the same trigger more than once in a short time window, you may see duplicate events exported. Enable SDK deduplication to automatically suppress duplicates.
Singular’s Web SDK supports optional event deduplication to reduce duplicate exports caused by repeated triggers fired within a short window. When enabled, the SDK will drop duplicate events that match the same deduplication parameters within the configured time window.
How to enable
-
withEventsDedupEnabled: Enable event deduplication (default is disabled). -
withTimeBetweenEvents: Maximum time window (in milliseconds) used to consider two events duplicates; default is 1000ms (1 second).
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);
How duplicates are detected
When deduplication is enabled, the SDK computes a hash from the event’s key fields and suppresses repeats within the time window. The deduplication parameters include: EventName, EventProductName, IsRevenueEvent, CustomUserId, GlobalProperties, MatchId, and WebUrl (and the SDK also accounts for additional event “extra” payload such as revenue/args when hashing).
Step 6: Testing Your Implementation
After implementing the SDK, verify that it's working correctly using your browser's developer tools.
Verify SDK Loaded
- Open your website in a browser
- Open Developer Tools (F12 or Right-click → Inspect)
- Go to the Console tab
- Type
typeof singularSdkand press Enter - You should see a "function" object, not "undefined"
Verify Network Requests
- Open Developer Tools (F12)
- Go to the Network tab
- Reload your page
- Filter by
singularorsdk-api - Look for requests to
sdk-api-v1.singular.net - Click on a request to view details
- Verify the Status Code is
200 - Check the request payload contains your Product ID. This will be in the "
i" parameter of the payload.
SUCCESS! If you see requests to sdk-api-v1.singular.net with status code 200, your SDK is successfully sending data to Singular.
Verify Events
- Trigger an event on your website (click a button, submit a form, etc.)
- In the Network tab, look for a new request to
sdk-api-v1.singular.net - Click on the request and view the Payload or Request tab
-
Verify your event name "
n" parameter appears in the request
For End-to-End testing, use the Testing Console
You can test the Web SDK integration using SDK Console available in the Singular dashboard.
- In the Singular platform, go to Developer Tools > Testing Console.
- Click Add Device.
- Select the platform "Web" and enter the Singular Device ID.
- Get the Singular Device ID from the browser payload. Check the above screenshot to locate the
SDIDon an event. - The testing console must remain in active state while you test in a separate window. The console will not display any events that are triggered while it is closed or offline.
- Once the SDID is added, you can reload the webpage and trigger some events. The "__PAGE_VISIT__" and other events (if triggered) will be shown in SDK Console.
- Utilizing the Export logs (Reports & Insights Export Logs) is another way to verify the testing results. This data set is delayed by 1 hour.
Step 7: Implement Web-to-App Forwarding
Web-to-App Attribution Forwarding
Use the Singular WebSDK to track user journeys from your website to your mobile app, enabling accurate web campaign attribution to mobile app installs and reengagements. Follow these steps to set up web-to-app forwarding, including QR code support for desktop users.
- Follow our Website-to-Mobile App Attribution Forwarding Guide to configure the Singular WebSDK for mobile web attribution.
- For Desktop Web-to-App tracking:
- Complete the setup in the guide above.
- Use the Singular Mobile Web-to-App Link (e.g.,
https://yourlink.sng.link/...) and the WebSDK functionbuildWebToAppLink(link)with a dynamic QR code library like QRCode.js. - Generate a QR code on your webpage that encodes the link. Desktop users can scan it with their mobile device to open the app, passing campaign data for attribution.
- Explore our Singular WebSDK Demo for a working example of QR code generation and web-to-app link handling.
TIP! Mobile in-app browser web views (like those used by Facebook, Instagram, and TikTok) can cause the Singular Device ID to change if a user moves to the device’s native browser, disrupting attribution.
To prevent this, always use the proper Singular tracking link format for each ad network:
Singular WebSDK Demo
Below is a simple but comprehensive implementation using the documented Singular WebSDK API. This sample provides clear separation for custom events, conversion events, revenue events, and web-to-app link support using WebSDK supported Web-to-App forwarding.
You can run this code in a local HTML file or modify it in your site for advanced integration and troubleshooting.
Copy and paste the code below as an HTML file and open it in your browser.
<!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/en-us/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>
Advanced Topics
Singular Banners
INFO: Singular Banners is an enterprise feature. To learn more about this feature, reach out to your Customer Success Manager.
Singular Banners can be displayed in your mobile website to lead web users seamlessly to your app and display the most relevant app content. Once you enable Singular Banners in your website, your organization can easily design, deploy, and maintain banners through the Singular Banners UI.
Related Articles
Step-by-Step Implementation Guide
-
Add the Singular Native JavaScript WebSDK Script to your website.
Follow the Integration guide above to add the Singular Native JavaScript WebSDK to your website before proceeding with the Banners implementation.
(To implement Banners using Google Tag Manager, follow this guide.)
-
Give Singular Permission to Access Client Hints Data
With the limitations on user agent data in Chromium-based web browsers, rolled out in February-March 2023, advertisers now need to fetch client hints data as well as give the Singular WebSDK permission to receive this data. For more information, see the Banners FAQ.
The website needs to:
-
Start asking for Client Hints (
accept-ch header). -
Grant Singular permission (as a third party) so that
the
browser will send Singular the client hints on the
fetch banner
requests (
permissions-policy header).
Add the following response headers to the page load response:
accept-ch: sec-ch-ua-model, sec-ch-ua-platform-version, sec-ch-ua-full-version-list permissions-policy: ch-ua-model=("https://sdk-api-v1.singular.net"), ch-ua-platform-version=("https://sdk-api-v1.singular.net"), ch-ua-full-version-list=("https://sdk-api-v1.singular.net") -
Start asking for Client Hints (
-
Add the Banner config option to the WebSDK Initialization
Singular Banners display smart app download prompts on your mobile website while preserving attribution from the original marketing source. When enabled with web-to-app support, the SDK tracks which ad network or campaign brought the user to your site, then attributes the subsequent app install back to that original source.
/** * Initialize Singular SDK with Banner support and web-to-app attribution. * This enables smart banners that direct mobile web users to download your app * while preserving the original marketing source (UTM parameters, ad network, etc.) * for proper attribution when the app is installed. * @param {string} sdkKey - Your Singular SDK Key * @param {string} sdkSecret - Your Singular SDK Secret * @param {string} productId - Your Product ID (e.g., com.your-website) */ function initSingularSDK() { // Enable web-to-app tracking to preserve attribution data var bannersOptions = new BannersOptions().withWebToAppSupport(); // Configure SDK with banner support var config = new SingularConfig(sdkKey, sdkSecret, productId) .withBannersSupport(bannersOptions); // Initialize the SDK window.singularSdk.init(config); // Display the smart banner window.singularSdk.showBanner(); } // Call on DOM ready document.addEventListener('DOMContentLoaded', function() { initSingularSDK(); }); -
Reshow Banner on Page Route (Single-Page Applications Only)
If your application is Single-Page Application, you have to hide and re-show the banner on each page route. This ensures that Singular delivers the appropriate banner for your web experience.
To hide and re-show the banner, use the following code:
window.singularSdk.hideBanner(); window.singularSdk.showBanner(); -
[Advanced Option] Customize Link Settings
Singular provides a way to personalize the links in the banner through code.
To personalize links:
-
Create a
LinkParamsobject and use one or more of the functions below. Do this before callingwindow.singularSdk.showBanner(). -
Then pass the
LinkParamsobject when you callwindow.singularSdk.showBanner().
Example:
// Define a LinkParams object let bannerParams = new LinkParams(); // Configure link options (see details on each option below) bannerParams.withAndroidRedirect("androidRedirectValue"); bannerParams.withAndroidDL("androidDLParamValue"); bannerParams.withAndroidDDL("androidDDLparamValue"); bannerParams.withIosRedirect("iosRedirectValue"); bannerParams.withIosDL("iosDLValue"); bannerParams.withIosDDL("iosDDLValue"); // Show the banner with the defined options window.singularSdk.showBanner(bannerParams);List of options:
Method Description withAndroidRedirectPass a redirect link to your Android app download page, usually a Play Store page. withAndroidDLPass a deep link for a page within your Android app. withAndroidDDLPass a deferred deep link, i.e., a link to a page in your Android app that the user hasn't installed yet. withIosRedirectPass a redirect link to your iOS app download page, usually an App Store page. withIosDLPass a deep link to a page within your iOS app. withIosDDLPass a deferred deep link, i.e., a link to a page in your iOS app that the user hasn't installed yet. -
Create a
-
[Advanced Option] Use Code to Force Hiding/Showing of Banners
As mentioned in step 3, if you have a single-page application, you have to use the
hideBanner()andshowBanner()methods on each page route to ensure the appropriate banner is delivered (see above).hideBanner()andshowBanner()are also available to you to use throughout your code if you want to hide a banner that would otherwise be displayed, or re-show a banner that you hid.Method Description singularSdk.hideBanner()Hide a visible banner from your page. singularSdk.showBanner()Show the pre-configured banner. singularSdk.showBanner(params)Show the pre-configured banner but override links with the options defined in the linkParams object (see step 4).
Global Properties
The Singular SDK lets you define custom properties to be sent to the Singular servers along with every session and event sent from the app. These properties can represent any information you want about the user, the app mode/status, or anything else.
-
You can define up to 5 global properties as a valid JSON object. The global properties are persisted in the browsers
localstorageuntil it is cleared or the browser context is changed. -
Each property name and value can be up to 200 characters long. If you pass a longer property name or value, it will be truncated to 200 characters.
-
Global properties are currently reflected in Singular's user-level event logs (see Exporting Attribution Logs) and in postbacks.
-
Global properties are available to me sent in postbacks from Singular to a third party for matching purposes is needed.
To support setting Global Properties during the WebSDK initialization
you must implement the .withGlobalProperties()
option on the config object.
To handle Global Properites after initialization, you must use
the SDK functions: 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);
/**
* Set a Singular global property after SDK initialization.
* Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
*
* @param {string} propertyKey - The property key to set.
* @param {string} propertyValue - The property value to set.
* @param {boolean} overrideExisting - Whether to overwrite the property if it already exists.
*/
// Usage
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();
Cross-subdomain Tracking
By default, the Singular WebSDK generates a Singular Device ID and persists it using browser storage. Since this storage can't be shared between subdomains, the SDK ends up generating a new ID for each subdomain.
If you want to persist the Singular Device ID across subdomains, you can use one of the following options:
Method A: Auto-Persist using Cookies
You can have the Singular SDK persist the Singular Device ID using a custom first-party cookie. Use the following method and set the main domain you want to track.
| withAutoPersistentSingularDeviceId Method | |
|---|---|
| Description | Initialize the SDK with configuration options including the main domain for auto persisting Singular Device Id. |
| Signature | withAutoPersistentSingularDeviceId(domain) |
| Usage Example |
|
Method B (Advanced): Set Singular Device ID Manually
If you don’t want Singular SDK to persist the Device ID automatically, you can persist the ID manually across domains - for example, using a top-level domain cookie or a server-side cookie. The value should be an ID previously generated by Singular, in valid uuid4 format.
NOTE: You can read the Singular Device ID using singularSdk.getSingularDeviceId() after calling the init method or using InitFinishedCallback.
| withPersistentSingularDeviceId Method | |
|---|---|
|
Description |
Initialize the SDK with configuration options including the Singular Device Id you want to persist. |
| Signature | withPersistentSingularDeviceId(singularDeviceId) |
| Usage Example |
|
Next Steps
- Create website links in Singular for your web campaigns
- Follow our guides for Google Ads Web, Facebook Web, and TikTok Ads Web, if you run web campaigns for mobile inventory.
- Monitor your data in Singular reports