Overview
INFO: Web Attribution is an enterprise feature. Contact your Customer Success Manager to enable this feature for your account.
This guide explains how to implement the Singular Web SDK using Google Tag Manager (GTM). This method is ideal for teams without direct access to website code or those who want to manage tracking through GTM.
IMPORTANT! Do not use both GTM and Native JavaScript implementations on the same site. Choose only one method to prevent duplicate tracking and inflated event counts. Singular does not deduplicate events automatically.
- Do not implement both Native JavaScript and Google Tag Manager methods. Choose only one to avoid duplicate tracking.
- The Singular Web SDK 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.,
- Google Tag Manager has been configured on your site.
- Permission in GTM to edit your website's tags, triggers, and variables. Depending on customization, you may need to know how to create Custom tags, and configure Tag sequencing.
- If you do not have permission to publish changes to Google Tag Manager, you will need to ask for someone to publish the changes after testing.
- Knowledge on how to preview and debug containers and tags in GTM.
- 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 Singular Web Tracking templates to your GTM Container
- Log in to your Google Tag Manager account and select your website’s container.
- Go to Tags > New.
- Name the Tag: "Singular Init Tag"
- Click the Tag Configuration box to begin tag setup.
- Choose Tag Type: and select "Discover more tag types in the Community Template Gallery".
- Search for "Singular" and select "Singular Web Tracking". Click the "Add to Workspace" button.
Step 2: Initialize the SDK
-
Complete the form fields with the following:
- Set the Track Type to Initialization
- Enter your actual SDK Key for the Api Key field
- Enter your actual SDK Secret for the Secret field
-
Enter 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.TIP! use a specific Product ID for testing
com.website-name.devand update it before pushing to production. This keeps all of your test data separate from the production app in Singular reporting. -
Optional:
- Log Level: The configuration of SDK debug logging to console. The default is none.
- Session Timeout: How long the user has to be inactive before the SDK creates a new session. Singular sends user sessions to calculate user retention and enable re-engagement attribution. The default value is 30 minutes.
- Cross Sub-Domain Tracking
- Choose Triggering to configure a trigger to make this tag work.
- Choose New and name the Tigger: "Singular Init Trigger".
- Click the Trigger Configuration and choose "Page View - Window Loaded" and click "Save".
- Click "Save" again to save the Tag.
-
Click "Preview" from the Tag page and test that the
Singular
Initialization Tag is triggered.
SUCCESS! If you see the "Singular Init Tag" in the Tags Fired section of the Preview console, you have successfully configured the Initialization Tag.
IMPORTANT! for SPAs (Single Page Applications), you should trigger the Page Visit trackType every time you route to a different page. Do not call Page Visit on the first page that is loaded since Initialization already reports a page visit.
Solution Overview
- Use a custom JavaScript variable to detect if it’s the first page load.
-
Configure 2 Tags:
- "Singular Init Tag" (Track Type = Initialization) to fire only on the initial page load.
- "Singular Page Visit Tag" (Track Type = Page Visit) to fire on every route change (excluding the initial load) using the History Change trigger.
- Ensure your SPA pushes history events to the dataLayer for route changes.
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 specifically 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:
-
Create a new Tag for your custom Event using the Singular Web Tracking template.
-
Name the Event Tag.
-
Choose Track Type = Custom Event
-
Adjust the "Event Name" field or set the appropriate variable.
-
Adjust the "Custom User Id" field or set the appropriate variable.
-
Adjust the "Attributes" if desired to pass Key/Value pairs on the Event.
-
Configure a Trigger suitable to fire the Tag only when expected.
-
Save the Trigger and Tag, and test in the Preview.
Conversion Event Tracking
Track a conversion event:
-
Create a new Tag for your custom Event using the Singular Web Tracking template.
-
Name the Conversion Event Tag.
-
Choose Track Type = Conversion Event
-
Adjust the "Event Name" field or set the appropriate variable.
-
Adjust the "Custom User Id" field or set the appropriate variable.
-
Adjust the "Attributes" if desired to pass Key/Value pairs on the Event.
-
Configure a Trigger suitable to fire the Tag only when expected.
-
Save the Trigger and Tag, and test in the Preview.
Revenue Event Tracking
Track conversion events with revenue information and add optional attributes for more context:
-
Create a new Tag for your custom Event using the Singular Web Tracking template.
-
Name the Revenue Event Tag.
-
Choose Track Type = Revenue Event
-
Adjust the "Event Name" field or set the appropriate variable.
-
Adjust the "Custom User Id" field or set the appropriate variable.
-
Adjust the "Currency" field or set the appropriate variable. Pass revenue currency as a three-letter ISO 4217 currency code, such as “USD”, “EUR”, or “INR".
-
Adjust the "Revenue" field or set the appropriate variable. The revenue must be represented in the stated currency.
-
Adjust the "Attributes" if desired to pass Key/Value pairs on the Event.
-
Configure a Trigger suitable to fire the Tag only when expected.
-
Save the Trigger and Tag, and test in the Preview.
Common Event Implementation Patterns
Creating GTM Triggers for Page Load Events
To implement the Singular Web SDK with Google Tag Manager, you'll need to create a page load trigger that fires when your pages load.
Quick Setup: In GTM, navigate to Triggers New Trigger Configuration and select "Page View" as your trigger type. For most implementations, choose "All Page Views" to fire the trigger on every page load.
For Complete Setup Instructions: Refer to Google's official Tag Manager documentation:
Creating GTM Triggers for Button Clicks
To track user interactions with buttons using the Singular Web SDK through Google Tag Manager, you'll need to create click triggers that fire when users click specific elements.
Quick Setup: In GTM, navigate to Triggers New Trigger Configuration and select "All Elements" to track clicks on any page element (buttons, links, images) or "Just Links" for HTML anchor elements only.
Enable Click Variables: Before creating click triggers, enable built-in click variables in GTM by going to Variables Configure Built-in Variables and selecting all options under "Clicks".
Target Specific Elements: For improved performance, use "Some Clicks" instead of "All Clicks" and add conditions based on Click ID, Click Classes, or Click Text to target specific buttons.
For Complete Setup Instructions: Refer to Google's official Tag Manager documentation:
Creating GTM Triggers for Form Submission Events
To track form completions using the Singular Web SDK through Google Tag Manager, you'll need to create form submission triggers that fire when users successfully submit forms on your site.
Quick Setup: In GTM, navigate to Triggers New Trigger Configuration and select "Form Submission" as your trigger type. Enable "Check Validation" to ensure tracking only occurs for successful form submissions, and consider enabling "Wait for Tags" with a 2000ms delay to allow tracking before page redirects.
Enable Form Variables: Before creating form triggers, enable built-in form variables in GTM by going to Variables Configure Built-in Variables and selecting all options under "Forms".
Performance Optimization: For better performance, select "Some Forms" instead of "All Forms" and add conditions to target specific forms or pages where form submissions occur.
Alternative Methods: Note that modern AJAX forms may not work with standard form submission triggers. If the built-in trigger doesn't fire, consider alternative tracking methods like element visibility triggers for success messages or thank you page tracking.
For Complete Setup Instructions: Refer to Google's official Tag Manager documentation:
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.
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.
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.
As long as the user performs actions on your website without being logged in, events are sent to Singular with a Singular-generated user ID. But after the user registers or logs in, you can have events sent to Singular along with the user ID that is used on your website, e.g., a hashed email address.
Singular uses the user ID in user-level data exports (see Exporting Attribution Logs) as well as internal BI postbacks, if you have them configured (see Configuring Internal BI Postbacks).
There are two ways to send the user ID to Singular:
- Recommended: If you know the user ID when the website opens, set the user ID in the Initialization track type when initializing the SDK. This makes the user ID available to Singular from the very first page visit.
- Alternatively, you can trigger a Tag with Track Type = Login at any point, usually after an authentication occurs. We recommend calling it as soon as the user ID becomes available. Note: calling this Tag does not trigger an event. It only sets the user id to be added to any event triggers going forward!
To set the user ID with Singular, add a Singular tag with the "Login" track type:
- In your Google Tag Manager account, click Tags > New.
- In the Tag Configuration window, click Tag Configuration, and in the Tag Type menu, select "Singular Web Tracking".
- Under Track Type, select "Login".
- Under Custom User Id, enter the Google Tag Manager variable that contains the user ID.
- Click Triggering and add the triggering event: user login or registration.
- Click Save.
To unset the user ID, add a tag with the "Logout" track type:
- In your Google Tag Manager account, click Tags > New.
- In the Tag Configuration window, click Tag Configuration, and in the Tag Type menu, select "Singular Web Tracking".
- Under Track Type, select "Logout".
- Click Triggering and add the triggering event: user logout.
- Click Save.
Notes:
- The user ID persists until you unset it using the logout track type or until the user deletes their local storage.
- Closing/refreshing the website does not unset the user ID.
- Browsing in private mode such as incognito will prevent Singular from persisting the user ID, because the local storage is deleted automatically when closing the browser.
Step 5: Preventing Duplicate Events
Preventing Duplicate Events in Google Tag Manager
IMPORTANT! This is one of the most common GTM implementation errors. Without proper safeguards, Singular Web SDK events can fire multiple times when users refresh pages, navigate back, or retrigger actions, severely inflating your metrics.
Why Duplicates Occur in GTM
Google Tag Manager evaluates triggers on every page load, causing tags to re-fire when users refresh thank you pages, resubmit forms, or navigate using browser back/forward buttons. This happens because GTM lacks built-in session awareness for custom events.
GTM Deduplication Methods
Session Storage Approach: Store unique event identifiers in the browser's sessionStorage to track which events have already fired during the user session.
Custom JavaScript Variables: Create variables that check localStorage for previously fired events before allowing new ones to trigger.
Trigger Conditions: Add conditional logic to triggers that prevents firing when duplicate indicators are present.
Implementation Steps
Create Storage Variable: Build a Custom JavaScript Variable that generates unique identifiers for each Singular event based on event parameters and user session.
Build Check Variable: Create another Custom JavaScript Variable that checks if the current event identifier already exists in browser storage.
Add Trigger Conditions: Modify your Singular Web SDK triggers to include a condition that the "duplicate check" variable equals "false".
Store After Firing: Use GTM's Tag Sequencing feature to fire a Custom HTML tag that stores the event identifier in sessionStorage after the Singular event successfully fires.
GTM-Specific Considerations
Tag Sequencing: Use Advanced Settings Tag Sequencing to ensure storage tags fire after your main Singular tags complete.
Variable Evaluation: Remember that Custom JavaScript Variables evaluate each time they're referenced, so optimize for performance.
Cross-Domain Limitations: SessionStorage is domain-specific, so implement cookie-based solutions for cross-domain tracking if needed.
For Detailed Implementation: Refer to comprehensive GTM deduplication guides:
- Prevent Duplicate Form Submissions with Google Tag Manager - Analytics Mania
- Prevent Tags from Firing After Page Reload - Analytico Digital
Step 6: Testing Your GTM Implementation
- Open GTM Preview Mode and load your website.
- Check that:
-
The SDK loads (network request to
singular-sdk.js) - Events are triggered as expected and only once per action
-
No errors in browser console relating to
singular -
Network requests are sent to
sdk-api-v1.singular.net
- Use the Network tab in browser developer tools to verify the proper payload is sent
SUCCESS! If you see correct Singular events in the network requests and no duplicates, you are ready to go live!
Step 7: Implement Web-to-App Forwarding
Web-to-App Attribution Forwarding
Use the Singular Web SDK 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 Web SDK for mobile web attribution.
-
For Desktop Web-to-App tracking:
-
Creating a QR Code Generator Cleanup Tag
- Navigate to Tags > New in GTM and select Custom HTML.
-
Add code to:
- Inject the QRCode library you choose
-
Retrieve the Web-to-App Link from
window.singularSdk.buildWebToAppLink(baselink); - Generate the QRCode from the link returned.
- Update the QRCode image on the page.
- Name the tag: "Singular - QR Code Generator (Cleanup)"
- No trigger needed: Cleanup tags inherit triggers from the main tag
-
Configure your Singular Init tag:
- Click on Tag Configuration
- Go to Advanced Settings > Tag Sequencing
- Check "Fire a cleanup tag after [Singular Init Tag] fires"
- Select your QR Code Generator tag from the dropdown
- Save the tag, and test in Preview mode.
-
Creating a QR Code Generator Cleanup Tag
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:
Advanced Topics
Adding 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.
Setting a global property before SDK initialization is currently
not supported with the Google Tag Manager Initialization Tag.
However, to handle Global Properites after initialization, you
must create Custom HTML Tags and execute the Native JavaScript
function: setGlobalProperties(),
getGlobalProperties(),
clearGlobalProperties().
Creating a Custom HTML Tag for handling Global Properties
-
In your Google Tag Manager interface, click Tags in the left navigation menu, then click the New button to create a new tag.
-
Click Tag Configuration and select Custom HTML from the list of available tag types.
-
In the HTML field, paste the Global Property Method code. See the options below:
-
Click Triggering and assign a trigger option to the tag.
-
Give your tag a descriptive name like "Singular - Set Global Properties" in the tag name field at the top.
Setting the Global Properties will persist the Global Property on all events until unset or cleared.
Click Preview to test your implementation. Verify in the browser console that the global properties are set.
For Advanced Configuration: Refer to Google's official Custom HTML documentation
Custom HTML Tag code for Setting Global Properties
<script>
/**
* Set a Singular global property before SDK initialization.
* Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
*
* @param {string} propertyKey - The property key to set.
* @param {string} propertyValue - The property value to set.
* @param {boolean} overrideExisting - Whether to overwrite the property if it already exists.
*/
// Example usage - customize these values for your implementation
window.singularSdk.setGlobalProperties('user_type', 'premium', true);
window.singularSdk.setGlobalProperties('app_version', '2.1.0', false);
</script>
Custom HTML Tag code for Getting Global Properties
<script>
// Get all global properties as a JSON object
var globalProps = window.singularSdk.getGlobalProperties();
console.log('Current global properties:', globalProps);
</script>
Custom HTML Tag code for Clearing Global Properties
<script>
// Clears all Global Properties.
// Example usage
clearGlobalProperties();
</script>
Organic Search Tracking
Creating the Organic Search Tracking Setup Tag
CRITICAL! - This Tag Must Fire Before Singular SDK Initialization!
This Custom HTML tag modifies the document URL to add organic search tracking parameters (wpsrc and wpcn) that the Singular Web SDK needs to read during initialization. Tag sequencing is essential to ensure proper execution order.
This example is provided as a workaround soluton to enable Organic Search tracking. The code should be used as an example only and updated and maintained by the web developer based on the needs of your marketing department. Organic Search tracking may have different meanings per advertiser. Please review the sample and adjust for your needs.
Why Use This?
-
Ensures organic search visits are properly tracked, even if no campaign parameters are present.
-
Appends the Singular "Source" parameter
wpsrcwith the value from the (referrer) and the "Campaign Name" parameterwpcnas "OrganicSearch" to the URL for clear attribution. -
Stores the current URL and referrer in
localStoragefor later use. -
Pure JavaScript, zero dependencies, and easy to integrate.
How It Works
-
Checks the page URL for known campaign parameters from (Google, Facebook, TikTok, UTMs, etc.).
-
If no campaign parameters are present and the referrer is a search engine, appends:
-
wpsrc(with the referrer as its value) -
wpcn(with OrganicSearch as its value)
-
-
Updates the URL in the browser without reloading the page.
-
Stores the current URL and referrer in
localStorageassng_urlandsng_ref.
Usage
- Navigate to Tags > New in GTM and click Tag Configuration, then select Custom HTML.
- Paste the complete JavaScript code as shown below.
-
Skip the trigger creation for this tag. Since this tag must run before the Singular Init Tag, we will configure it to fire before the Init using Tag Sequencing.
- Use a descriptive name like "Singular - Organic Search Setup".
- Open your Singular Web SDK Init Tag.
- Click on Tag Configuration
- Go to Advanced Settings > Tag Sequencing.
- Check "Fire a setup tag before [Singular Init Tag] fires".
- Select your "Singular - Organic Search Setup" tag from the dropdown.
- Recommended: Check "Don't fire [Singular Init Tag] if [setup tag] fails" to prevent initialization with incomplete URL modification.
- Save the Tag.
-
Use GTM's Preview mode to verify:
- The setup tag fires first and modifies the URL.
- The Singular Init tag fires second and reads the modified URL parameters.
- No timing conflicts occur between the tags.
For Advanced Tag Sequencing: Refer to Google's official documentation:
<script>
(function() {
// singular-web-organic-search-tracking: setupOrganicSearchTracking.js
// Tracks organic search referrals by appending wpsrc and wpcn to the URL if no campaign parameters exist and the referrer is a search engine.
// Configuration for debugging (set to true to enable logs)
var debug = true;
// List of campaign parameters to check for exclusion
var campaignParams = [
'gclid', 'fbclid', 'ttclid', 'msclkid', 'twclid', 'li_fat_id',
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'wpsrc'
];
// Whitelist of legitimate search engine domains (prevents false positives)
var legitimateSearchEngines = new Set([
// Google domains
'google.com', 'google.co.uk', 'google.ca', 'google.com.au', 'google.de',
'google.fr', 'google.it', 'google.es', 'google.co.jp', 'google.co.kr',
'google.com.br', 'google.com.mx', 'google.co.in', 'google.ru', 'google.com.sg',
// Bing domains
'bing.com', 'bing.co.uk', 'bing.ca', 'bing.com.au', 'bing.de',
// Yahoo domains
'yahoo.com', 'yahoo.co.uk', 'yahoo.ca', 'yahoo.com.au', 'yahoo.de',
'yahoo.fr', 'yahoo.it', 'yahoo.es', 'yahoo.co.jp',
// Other search engines
'baidu.com', 'duckduckgo.com', 'yandex.com', 'yandex.ru',
'ask.com', 'aol.com', 'ecosia.org', 'startpage.com',
'qwant.com', 'seznam.cz', 'naver.com', 'daum.net'
]);
// Extract main domain from hostname (removes subdomains)
function getMainDomain(hostname) {
if (!hostname) return '';
var lowerHost = hostname.toLowerCase();
// Handle special cases for known search engines with country codes
var searchEnginePatterns = {
'google': function(host) {
// Match google.TLD patterns more precisely
if (host.indexOf('google.co.') !== -1 || host.indexOf('google.com') !== -1) {
var parts = host.split('.');
for (var i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'google') {
return parts.slice(i).join('.');
}
}
}
return null;
},
'bing': function(host) {
if (host.indexOf('bing.co') !== -1 || host.indexOf('bing.com') !== -1) {
var parts = host.split('.');
for (var i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'bing') {
return parts.slice(i).join('.');
}
}
}
return null;
},
'yahoo': function(host) {
if (host.indexOf('yahoo.co') !== -1 || host.indexOf('yahoo.com') !== -1) {
var parts = host.split('.');
for (var i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'yahoo') {
return parts.slice(i).join('.');
}
}
}
return null;
}
};
// Try specific patterns for major search engines
for (var engine in searchEnginePatterns) {
if (lowerHost.indexOf(engine) !== -1) {
var result = searchEnginePatterns[engine](lowerHost);
if (result) return result;
}
}
// Handle other known engines with simple mapping
var otherEngines = {
'baidu.com': 'baidu.com',
'duckduckgo.com': 'duckduckgo.com',
'yandex.ru': 'yandex.ru',
'yandex.com': 'yandex.com',
'ask.com': 'ask.com',
'aol.com': 'aol.com',
'ecosia.org': 'ecosia.org',
'startpage.com': 'startpage.com',
'qwant.com': 'qwant.com',
'seznam.cz': 'seznam.cz',
'naver.com': 'naver.com',
'daum.net': 'daum.net'
};
for (var domain in otherEngines) {
if (lowerHost.indexOf(domain) !== -1) {
return otherEngines[domain];
}
}
// Fallback: Extract main domain by taking last 2 parts (for unknown domains)
var parts = hostname.split('.');
if (parts.length >= 2) {
return parts[parts.length - 2] + '.' + parts[parts.length - 1];
}
return hostname;
}
// Get query parameter by name, using URL.searchParams with regex fallback for IE11
function getParameterByName(name, url) {
if (!url) url = window.location.href;
try {
return new URL(url).searchParams.get(name) || null;
} catch (e) {
if (debug) console.warn('URL API not supported, falling back to regex:', e);
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
var results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
}
// Check if any campaign parameters exist in the URL
function hasAnyParameter(url, params) {
for (var i = 0; i < params.length; i++) {
if (getParameterByName(params[i], url) !== null) {
return true;
}
}
return false;
}
// Improved search engine detection - only checks hostname, uses whitelist
function isSearchEngineReferrer(referrer) {
if (!referrer) return false;
var hostname = '';
try {
hostname = new URL(referrer).hostname.toLowerCase();
} catch (e) {
// Fallback regex for hostname extraction (IE11 compatibility)
var match = referrer.match(/^(?:https?:\/\/)?([^\/\?#]+)/i);
hostname = match ? match[1].toLowerCase() : '';
}
if (!hostname) return false;
// First check: exact match against whitelist
if (legitimateSearchEngines.has(hostname)) {
if (debug) console.log('Exact match found for:', hostname);
return true;
}
// Second check: subdomain of legitimate search engine
var hostParts = hostname.split('.');
if (hostParts.length >= 3) {
// Try domain.tld combination (e.g., google.com from www.google.com)
var mainDomain = hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
if (legitimateSearchEngines.has(mainDomain)) {
if (debug) console.log('Subdomain match found for:', hostname, '-> main domain:', mainDomain);
return true;
}
// Try last 3 parts for country codes (e.g., google.co.uk from www.google.co.uk)
if (hostParts.length >= 3) {
var ccDomain = hostParts[hostParts.length - 3] + '.' + hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
if (legitimateSearchEngines.has(ccDomain)) {
if (debug) console.log('Country code domain match found for:', hostname, '-> cc domain:', ccDomain);
return true;
}
}
}
if (debug) {
console.log('Hostname not recognized as legitimate search engine:', hostname);
}
return false;
}
// Main function to update URL with organic search tracking parameters
function setupOrganicSearchTracking() {
var url = window.location.href;
var referrer = document.referrer || '';
// Store URL and referrer in localStorage
try {
localStorage.setItem('sng_url', url);
localStorage.setItem('sng_ref', referrer);
} catch (e) {
if (debug) console.warn('localStorage not available:', e);
}
if (debug) {
console.log('Current URL:', url);
console.log('Referrer:', referrer);
}
// Skip if campaign parameters exist or referrer is not a search engine
var hasCampaignParams = hasAnyParameter(url, campaignParams);
if (hasCampaignParams || !isSearchEngineReferrer(referrer)) {
if (debug) console.log('Skipping URL update: Campaign params exist or referrer is not a legitimate search engine');
return;
}
// Extract and validate referrer hostname
var referrerHostname = '';
try {
referrerHostname = new URL(referrer).hostname;
} catch (e) {
if (debug) console.warn('Invalid referrer URL, falling back to regex:', e);
var match = referrer.match(/^(?:https?:\/\/)?([^\/]+)/i);
referrerHostname = match ? match[1] : '';
}
// Extract main domain from hostname
var mainDomain = getMainDomain(referrerHostname);
if (debug) {
console.log('Full hostname:', referrerHostname);
console.log('Main domain:', mainDomain);
}
// Only proceed if main domain is valid and contains safe characters
if (!mainDomain || !/^[a-zA-Z0-9.-]+$/.test(mainDomain)) {
if (debug) console.log('Skipping URL update: Invalid or unsafe main domain');
return;
}
// Update URL with wpsrc and wpcn parameters
var urlObj;
try {
urlObj = new URL(url);
} catch (e) {
if (debug) console.warn('URL API not supported, cannot modify URL:', e);
return;
}
// Set wpsrc to the main domain (e.g., google.com instead of tagassistant.google.com)
urlObj.searchParams.set('wpsrc', mainDomain);
// Set wpcn to 'Organic Search' to identify the campaign type
urlObj.searchParams.set('wpcn', 'Organic Search');
// Update the URL without reloading (check if history API is available)
if (window.history && window.history.replaceState) {
try {
window.history.replaceState({}, '', urlObj.toString());
if (debug) console.log('Updated URL with organic search tracking:', urlObj.toString());
} catch (e) {
if (debug) console.warn('Failed to update URL:', e);
}
} else {
if (debug) console.warn('History API not supported, cannot update URL');
}
}
// Execute the function
setupOrganicSearchTracking();
})();
</script>
Cross-subdomain Tracking
By default, the Singular Website SDK 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 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 by defining a custom JavaScript variable and calling singularSdk.getSingularDeviceId() after calling the Init track-type tag.
Common GTM Implementation Issues
| Required Device Identifiers | |
|---|---|
| Event fires multiple times | Refine or limit your triggers, use “Once per page” or add conditions |
| Incorrect Product ID format |
Product ID must use reverse DNS notation (com.company.site)
|
| Events not tracking |
Check spelling and case of event names; ensure singular
SDK is loaded before event tag fires
|
| SDK script blocked | Ad blockers or restrictive Content Security Policy; consider moving to the native JS implementation if persistent issues |
Best Practices
- Keep GTM organized: Name tags and triggers clearly and document their purpose.
- Audit for unused or legacy tags regularly.
- Minimize the number of triggers to reduce risk of duplicate events.
- After changes, always test thoroughly in GTM’s Preview Mode before pushing live.
- If you use cookie-based tracking (Cross-subdomain Tracking), update your privacy policy accordingly.