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:
  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: Event Deduplication (Optional)

Event Deduplication in Google Tag Manager

IMPORTANT! If your GTM triggers can fire the same Singular event multiple times within a short window (for example, rapid repeated clicks or multiple triggers for the same action), enable Singular’s optional Event Deduplication to automatically suppress duplicate exports.

Why Duplicates Occur in GTM

GTM can evaluate and fire tags multiple times for the same user action (for example, repeated trigger conditions or multiple event listeners), which can lead to duplicate Singular event exports.


GTM Deduplication Method (Recommended)

Singular SDK Event Deduplication: Enable deduplication in the Singular Web SDK GTM template to drop duplicate events that match the same deduplication parameters within a configurable time window.


Implementation Steps

Enable Event Deduplication: In your Singular Web SDK Initialization tag/template, enable the Event Deduplication option.

eventDeduplication.png

Set the Time Window (Optional): Configure the maximum time window (in milliseconds) used to consider two events duplicates (default: 1000ms / 1 second).


How deduplication works

When enabled, the SDK suppresses events (excluding page visits) when the same event occurs again within the time window using a hash of these parameters: EventName, EventProductName, IsRevenueEvent, CustomUserId, GlobalProperties, MatchId, and WebUrl (plus the event "extra" payload such as revenue and custom arguments).


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 Mobile Web-to-App tracking:
    • Add the Open App Tag and set the firing trigger on the button click to open or install your app.
      1. When configuring the Open App tag, specify your Mobile Web-to-App base link from the Singular Manage Links page.

        openApp.png

  3. For Desktop Web-to-App tracking:
    • Create 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

Singular Banners

Enable 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

  1. Add the Singular WebSDK GTM Initialization Tag to your website.

    Follow the Integration guide above to add the Singular GTM WebSDK to your website before proceeding with the Banners implementation.

  2. In the Intitalization Tag configuration, enable the Smart Banners and Web-to-App Support. It is advised to set the Advanced Settings Tag firing priority to 99. This will prioritize the Initialization before other tags with the same Firing Trigger.

    gtm-banners.png

  3. Add the Show Banner Tag

    To show the Smart Banner after the page loads, add a Show Banner Tag triggered on All Page Views. It is advised to set the Advanced Settings Tag firing priority to 1, which delays this tag to fire last.

    showBanner.png

    To hide a banner from showing on a page, add the Hide Banner Tag, and set a trigger based on your needs.

  4. [Advanced Option] Customize Link Settings

    Singular provides a way to personalize the links in the banner through code.

    To personalize links:

    • In the Show Banner tag configuration, expand the "Link Parameters (Optional) section. You may add specific override values for the redirection logic of the banner click action.

      bannerRedirects.png

    List of options:

    Method Description
    Android Redirect URL Pass a redirect link to your Android app download page, usually a Play Store page.
    Android Deep Link Pass a deep link for a page within your Android app.
    Android Deferred Deep Link Pass a deferred deep link, i.e., a link to a page in your Android app that the user hasn't installed yet.
    iOS Redirect URL Pass a redirect link to your iOS app download page, usually an App Store page.
    iOS Deep Link Pass a deep link to a page within your iOS app.
    iOS Deferred Deep Link Pass a deferred deep link, i.e., a link to a page in your iOS app that the user hasn't installed yet.

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. These properties can represent information about the user, app mode/status, or anything else you choose.

  • You can define up to 5 global properties as a valid JSON object. Global properties are persisted in the browser’s localStorage until it is cleared.

  • 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 reflected in Singular's user-level event logs and in postbacks.

Global Properties are now supported in the Google Tag Manager tracking template during initialization. In the Singular GTM Initialization tag type, set a JSON object of properties and choose whether to override existing properties.

Global Properties tag types in GTM

Use the Initialization tag type to set Global Properties on init. For runtime updates after initialization, use the dedicated Global Properties tag types (Set, Get, Unset, Clear).

Set on InitializationSet Global PropertiesGet Global PropertiesUnset Global PropertyClear Global Properties

Initialization Tag configuration

In the Singular GTM Initialization tag type, set the Global Properties (object) by assigning variables, datalayer values, or text in the Key and Value fields. Adjust the Override (true/false) field based on your needs. When Override is false, the existing property is not changed; when true, existing property is replaced.

gpgtm.png


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