Web SDK - Google Tag Manager Implementation Guide

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:

Implementation Steps


Step 1: Add the Singular Web Tracking templates to your GTM Container

  1. Log in to your Google Tag Manager account and select your website’s container.
  2. Go to Tags > New.
  3. Name the Tag: "Singular Init Tag"
  4. Click the Tag Configuration box to begin tag setup.
  5. Choose Tag Type: and select "Discover more tag types in the Community Template Gallery".
  6. Search for "Singular" and select "Singular Web Tracking". Click the "Add to Workspace" button.

Step 2: Initialize the SDK

  1. 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-name and 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.dev and 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
  2. Choose Triggering to configure a trigger to make this tag work.
  3. Choose New and name the Tigger: "Singular Init Trigger".
  4. Click the Trigger Configuration and choose "Page View - Window Loaded" and click "Save".
  5. Click "Save" again to save the Tag.
  6. Click "Preview" from the Tag page and test that the Singular Initialization Tag is triggered.

    singular_init_tag_fired.png

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.

image5.png


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 EventConversion EventRevenue Event

Basic Event Tracking

Track a simple event or add custom attributes using valid JSON to provide more context about the event:

  1. Create a new Tag for your custom Event using the Singular Web Tracking template.

  2. Name the Event Tag.

  3. Choose Track Type = Custom Event

  4. Adjust the "Event Name" field or set the appropriate variable.

  5. Adjust the "Custom User Id" field or set the appropriate variable.

  6. Adjust the "Attributes" if desired to pass Key/Value pairs on the Event.

  7. Configure a Trigger suitable to fire the Tag only when expected.

  8. Save the Trigger and Tag, and test in the Preview.

custom_event.png

Common Event Implementation Patterns

Page Load EventsButton Click EventsForm Submission Events

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:


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:

  1. In your Google Tag Manager account, click Tags > New.
  2. In the Tag Configuration window, click Tag Configuration, and in the Tag Type menu, select "Singular Web Tracking".
  3. Under Track Type, select "Login".
  4. Under Custom User Id, enter the Google Tag Manager variable that contains the user ID.
  5. Click Triggering and add the triggering event: user login or registration.
  6. Click Save.

image4.png

To unset the user ID, add a tag with the "Logout" track type:

  1. In your Google Tag Manager account, click Tags > New.
  2. In the Tag Configuration window, click Tag Configuration, and in the Tag Type menu, select "Singular Web Tracking".
  3. Under Track Type, select "Logout".
  4. Click Triggering and add the triggering event: user logout.
  5. Click Save.

image1.png

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:


Step 6: Testing Your GTM Implementation

  1. Open GTM Preview Mode and load your website.
  2. 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
  1. 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.

  1. Follow our Website-to-Mobile App Attribution Forwarding Guide to configure the Singular Web SDK for mobile web attribution.
  2. For Desktop Web-to-App tracking:
    • Creating a QR Code Generator Cleanup Tag
      1. Navigate to Tags > New in GTM and select Custom HTML.
      2. 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.
      3. Name the tag: "Singular - QR Code Generator (Cleanup)"
      4. No trigger needed: Cleanup tags inherit triggers from the main tag
      5. 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.

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

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 localstorage until 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

  1. In your Google Tag Manager interface, click Tags in the left navigation menu, then click the New button to create a new tag.

  2. Click Tag Configuration and select Custom HTML from the list of available tag types.

  3. In the HTML field, paste the Global Property Method code. See the options below:

  4. Click Triggering and assign a trigger option to the tag.

  5. 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

Setting Global PropertiesGetting GlobalPropertiesClearing Global Properties

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>

Organic Search Tracking

Organic Search Tracking Example
#

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 wpsrc with the value from the (referrer) and the "Campaign Name" parameter wpcn as "OrganicSearch" to the URL for clear attribution.

  • Stores the current URL and referrer in localStorage for later use.

  • Pure JavaScript, zero dependencies, and easy to integrate.

How It Works

  1. Checks the page URL for known campaign parameters from (Google, Facebook, TikTok, UTMs, etc.).

  2. 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)
  3. Updates the URL in the browser without reloading the page.

  4. Stores the current URL and referrer in localStorage as sng_url and sng_ref.

Usage

  1. Navigate to Tags > New in GTM and click Tag Configuration, then select Custom HTML.
  2. Paste the complete JavaScript code as shown below.
  3. 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.

    1. Use a descriptive name like "Singular - Organic Search Setup".
    2. Open your Singular Web SDK Init Tag.
    3. Click on Tag Configuration
    4. Go to Advanced Settings > Tag Sequencing.
    5. Check "Fire a setup tag before [Singular Init Tag] fires".
    6. Select your "Singular - Organic Search Setup" tag from the dropdown.
    7. Recommended: Check "Don't fire [Singular Init Tag] if [setup tag] fails" to prevent initialization with incomplete URL modification.
    8. organic_search.png
    9. Save the Tag.
  4. 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:


Organic Search Tracking Code
<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
#

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.

mceclip2.png


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.

Related Articles