Web SDK - Google 标签管理器实施指南

文件

概述

INFO:网络归因是企业级功能。请联系您的客户成功经理为您的账户启用此功能。

本指南说明如何通过Google Tag Manager(GTM)实现Singular Web SDK的集成。此方法适用于无法直接访问网站代码的团队,或希望通过GTM管理跟踪的团队。

重要提示!在同一网站同时使用GTM和原生JavaScript两种实现方式。仅选择其中一种方法,以避免重复跟踪和事件计数虚高。Singular不会自动去重事件。

  • 请勿同时采用原生JavaScript与Google Tag Manager两种方法,仅选择其中一种以避免重复跟踪。
  • Singular Web SDK专为用户浏览器端运行设计,需访问localStorage文档对象模型 (DOM)等浏览器功能才能正常运作。请勿尝试在服务器端运行 SDK(例如通过 Next.js SSR 或 node.js),因服务器环境无法访问浏览器 API,将导致追踪失败。

前提条件

开始前请确保您拥有:

实施步骤


步骤1:将Singular网络追踪模板添加至GTM容器

  1. 登录您的Google Tag Manager账户并选择网站容器。
  2. 前往标签>新建
  3. 为标签命名:"Singular初始化标签"
  4. 点击标签配置框开始设置
  5. 选择标签类型:点击"在社区模板库中探索更多标签类型"
  6. 搜索"Singular"并选择"Singular Web Tracking"。点击"添加到工作区"按钮。

步骤 2:初始化 SDK

  1. 填写表单字段如下:
    • 跟踪类型设为初始化
    • 在Api Key字段输入实际SDK密钥
    • 在密钥字段输入实际SDK密钥 字段
    • 输入实际产品ID。格式应为:com.website-name ,且需与Singular平台"应用程序"页面中的BundleID值一致。

      提示!测试时请使用专用产品ID com.website-name.dev , 正式发布前请更新该ID。 此举可确保测试数据与生产环境数据 在Singular报告中完全分离。

    • 可选:
  2. 选择"触发器"配置触发条件以使该 标签 生效。
  3. 选择新建命名触发器:"Singular 初始化触发器"。
  4. 点击触发器配置,选择"页面查看 - 窗口加载" 并点击"保存"。
  5. 再次点击"保存"以保存标签。
  6. 在标签页面点击"预览",测试 Singular 初始化标签是否被触发。

    singular_init_tag_fired.png

成功!若在预览控制台的"已触发标签"区域看到"Singular初始化标签",则表示初始化标签配置成功。

重要提示!对于 单页应用(SPA),每次跳转至新页面时 应触发 Page Visit跟踪类型。首次加载页面时 请勿 调用Page Visit因初始化已报告 该页面 访问。

解决方案概述

  • 使用自定义 JavaScript 变量检测是否为首次页面加载。
  • 配置两个标签:
    • "初始化标签"(追踪类型=初始化) 仅在首次页面加载时触发。
    • "单次页面访问标签"(追踪类型 = 页面 访问)通过历史记录变更触发器,在每次路由变更时触发(初始加载除外)。
  • 确保您的SPA在路由变更时将历史记录事件推送至dataLayer。

image5.png


步骤3:事件追踪

初始化 SDK 后,当用户在网站执行重要操作时即可追踪自定义事件。

重要提示!Singular不会拦截重复事件! 开发者需自行添加保护措施,防止页面刷新或重复事件触发。建议针对收入事件采用去重方法,避免收入数据错误。具体示例请参见下文"步骤5:防止重复事件"。

Basic EventConversion EventRevenue Event

基础事件追踪

通过有效JSON格式追踪基础事件或添加自定义属性, 为事件提供更多上下文信息:

  1. 使用Singular Web Tracking模板创建自定义事件标签。

  2. 命名事件标签。

  3. 选择跟踪类型 = 自定义事件

  4. 调整"事件名称"字段或设置相应变量。

  5. 调整"自定义用户ID"字段或设置相应变量。

  6. 根据需要调整"属性"以在事件中传递键值对。

  7. 配置触发器,确保标签仅在预期条件下触发。

  8. 保存触发器和标签,并在预览中测试。

custom_event.png

常见事件实现模式

Page Load EventsButton Click EventsForm Submission Events

创建页面加载事件的GTM触发器

要通过Google Tag Manager实现Singular Web SDK,需创建页面加载触发器,在页面加载时触发。

快速设置:在GTM中导航至 触发器 新建 触发器配置,选择 "页面浏览"作为触发器类型。对于大多数实现场景, 选择"所有页面浏览"以在每次页面加载时触发。

完整设置指南:请参阅Google官方标签管理器文档:


步骤4:设置客户用户ID

您可通过Singular SDK方法向Singular发送内部用户ID。

注意:若使用 Singular跨设备解决方案,必须在所有平台 收集用户 ID。

注意:若多个用户共用单一设备,建议 实现注销流程,在每次登录/注销时 设置 /清除用户ID。

提示!请使用与移动端SDK相同的客户用户ID。 这能实现跨设备归因,并提供完整的 跨平台用户行为视图。

当用户未登录状态下在网站执行操作时, 事件将通过Singular生成的用户ID发送至平台。 但用户注册或登录后, 可将网站使用的用户ID(如哈希化邮箱地址) 与事件同步发送至Singular。

在用户级数据导出(参见《导出归因日志》) 以及内部BI回传(若已配置,参见《配置内部BI回传》)中, Singular均会使用该用户ID。

向Singular发送用户ID有两种方式:

  • 推荐方案:若 网站加载时已知用户ID, 请在初始化SDK时 于初始化跟踪类型中设置用户ID。 此举可确保用户ID自首次页面访问起 即对Singular可用。
  • 替代方案:您可在任何时间点(通常在身份验证完成后)触发追踪类型为 "登录"的标签。建议在用户ID可用时立即调用该标签。 注意:调用此标签不会触发事件, 仅将用户ID设置为后续所有事件触发器的附加参数!

若需通过Singular设置用户ID,请添加"登录"追踪类型的Singular标签:

  1. 在您的 Google 标签管理器账户中,点击标签 > 新建
  2. 标签配置窗口中,点击 标签配置,并在标签类型 菜单中选择"Singular Web Tracking"。
  3. 在追踪类型下选择"登录"。
  4. 自定义用户ID下,输入包含用户ID的Google Tag Manager变量。
  5. 点击触发器并添加触发事件:用户 登录 或注册。
  6. 点击保存

image4.png

若需清除用户ID,请添加具有 "注销"跟踪类型的标签:

  1. 在您的 Google 标签管理器账户中,点击标签 > 新建
  2. 标签配置窗口中,点击 标签配置,并在标签类型 菜单中选择"单一网页跟踪"。
  3. 跟踪类型下选择"注销"。
  4. 点击触发器并添加触发事件:用户 注销。
  5. 点击保存

image1.png

注意事项: 

  • 用户ID将持续存在,直至您通过注销追踪类型清除该ID,或用户自行删除本地存储数据。
  • 关闭/刷新网站不会清除用户ID。
  • 使用隐身模式等私密浏览模式时,Singular将无法 持久化 用户ID,因浏览器关闭时本地存储会被自动清除。

步骤5:事件去重(可选)

Google Tag Manager中的事件去重

重要提示!若您的GTM触发器可能在短时间内多次触发同一Singular事件(例如快速重复点击或同一动作触发多个事件),请启用Singular的可选事件去重功能,自动抑制重复导出。

GTM 中出现重复数据的原因

GTM可能针对同一用户操作多次评估并触发标签(例如重复触发条件或多个事件监听器),导致Singular事件导出出现重复。


GTM去重方法(推荐)

Singular SDK事件去重:在Singular Web SDK的GTM模板中启用去重功能,可移除在可配置时间窗口内匹配相同去重参数的重复事件。


实施步骤

启用事件去重:在 Singular Web SDK 初始化标签/模板中启用事件去重选项。

eventDeduplication.png

设置时间窗口(可选):配置判定两个事件为重复的最大时间窗口(以毫秒为单位,默认:1000毫秒/1秒)。


去重机制原理

启用后,当相同事件在时间窗口内再次发生时,SDK将抑制该事件(页面访问除外)。抑制依据为以下参数的哈希值:EventNameEventProductNameIsRevenueEventCustomUserIdGlobalPropertiesMatchIdWebUrl(以及事件"额外"负载,如收入和自定义参数)。


步骤 6:测试 GTM 实施效果

  1. 开启GTM预览模式并加载您的网站。
  2. 检查以下事项:
  • SDK 是否加载成功(网络请求至singular-sdk.js
  • 事件触发符合预期且每项操作仅触发一次
  • 浏览器控制台无与singular相关的错误
  • 网络请求是否发送至sdk-api-v1.singular.net
  1. 使用浏览器开发者工具的"网络"选项卡验证 是否发送了正确的有效负载

成功!若在网络请求中看到正确的Singular事件且无重复项,即可上线!


步骤7:实现网页到应用的归因转发

网页到应用归因转发

使用Singular Web SDK追踪用户从网站到移动应用的旅程, 实现精准的网页广告活动归因至移动应用安装及 用户回访。 请按以下步骤配置网页到应用转发功能(含桌面端用户 二维码支持方案)。

  1. 请参照 《网站到移动应用归因转发指南》 配置 Singular Web SDK 以实现移动网页归因。
  2. 移动端网页到应用追踪操作:
    • 添加"打开应用"标签,并将触发器设置为 按钮点击事件,用于打开或安装应用。
      1. 配置"打开应用"标签时, 请在Singular管理链接页面 指定您的移动网页到应用基础链接。

        openApp.png

  3. 针对桌面端网页到应用的跟踪:
    • 创建二维码生成器清理标签
      1. 在GTM中导航至标签 > 新建, 选择 自定义HTML
      2. 在代码区域添加
        • 注入您选择的二维码库
        • 从以下链接获取网页到应用程序链接: window.singularSdk.buildWebToAppLink(baselink);
        • 根据返回的链接生成二维码。
        • 更新页面上的二维码图像。
      3. 标签命名:"单一 - QR码生成器 (清理版)"
      4. 无需触发器:清理标签继承 主标签的触发器
      5. 配置您的Singular初始化标签:
        • 点击标签配置
        • 前往高级设置 > 标签序列
        • 勾选"在[Singular初始化标签]触发后执行清理标签"
        • 从下拉菜单中选择您的二维码生成器标签
        • 保存标签,并在预览模式下测试。

提示!移动应用内浏览器网页视图(如 Facebook、Instagram 和TikTok所采用的)可能导致用户切换至设备原生浏览器时 改变 Singular设备ID,从而干扰归因。

为避免此问题,请始终为每个广告网络使用正确的Singular跟踪链接格式:


进阶主题

Singular横幅广告

启用单一横幅
#

说明:单一横幅广告为企业级功能。 如需了解详情,请联系您的客户成功经理。

在移动网站中展示Singular横幅, 可无缝引导网页用户进入应用, 并展示最相关的应用内容。 启用网站横幅功能后, 您的组织可通过Singular横幅界面 轻松设计、部署和维护横幅。

相关文章

分步实施指南

  1. 将Singular WebSDK GTM初始化标签添加至您的 网站。

    请先遵循 上述集成指南将Singular GTM WebSDK添加至网站,再进行横幅广告实施。

  2. 在初始化标签配置中,启用智能横幅和网页到应用支持功能。建议将高级设置标签触发优先级设为99,这将使初始化在具有相同触发条件的其他标签中获得优先处理权。

    gtm-banners.png

  3. 添加展示横幅标签

    为在页面加载后显示智能横幅,请添加 基于"所有页面浏览"触发的显示横幅标签。建议 将高级设置标签触发优先级设为1, 使该标签延迟至最后触发。

    showBanner.png

    若需隐藏特定页面横幅,添加隐藏横幅标签并根据需求设置触发条件。

  4. [高级选项] 自定义链接设置

    Singular支持通过代码实现横幅内链接的个性化设置。

    链接个性化操作:

    • 在"显示横幅"标签配置中展开 "链接参数(可选)"区域。 可为横幅点击行为的重定向逻辑 添加特定覆盖值。

      bannerRedirects.png

    选项列表:

    方法 描述
    Android Redirect URL 传递重定向链接至您的 Android 应用 下载 页面(通常为 Google Play 商店页面)。
    Android Deep Link 传递应用内页面的深度链接。
    Android Deferred Deep Link 传递延迟深层链接,即指向用户尚未安装的 Android应用内 页面的链接。
    iOS Redirect URL 传递重定向链接至您的iOS应用下载页面, 通常为App Store页面。
    iOS Deep Link 传递指向iOS应用内页面的深度链接。
    iOS Deferred Deep Link 传递延迟深层链接,即指向用户尚未安装的iOS应用内页面的链接。

添加全局属性

全局属性
#

Singular SDK 允许您定义自定义属性,这些属性将随每次会话和事件一同发送至 Singular 服务器。这些属性可代表用户信息、应用模式/状态或其他任意自定义内容。

  • 最多可定义5个全局属性,以有效JSON对象形式存储。全局属性将保存在浏览器的localStorage 中,直至该缓存被清除。

  • 每个属性名称和值最多可包含200个字符。 若传递的属性名称或值超出此限制, 将被截断为200个字符。

  • 全局属性将反映在Singular的用户级事件日志和回传数据中。

Google Tag Manager 现已在初始化阶段支持全局属性。在 Singular GTM初始化标签类型中,设置包含属性的 JSON 对象,并选择是否覆盖现有属性。

GTM中的全局属性标签类型

初始化时使用初始化标签类型设置全局属性。初始化后的运行时更新需使用专用全局属性标签类型(设置、获取、取消设置、清除)。

Set on InitializationSet Global PropertiesGet Global PropertiesUnset Global PropertyClear Global Properties

初始化标签配置

在SingularGTM初始化标签类型中,通过在KeyValue 字段中分配变量、数据层值或文本,设置Global Properties (对象)。根据需求调整Override (true/false)字段: 当Overridefalse 时,现有属性保持不变; 当true 时,现有属性将被覆盖。

gpgtm.png


自然搜索追踪

自然搜索追踪示例
#

创建自然搜索追踪设置标签

重要提示! - 此标签必须在Singular SDK初始化前触发!

此自定义HTML标签会修改文档URL,添加Singular Web SDK在初始化期间需要读取的自然搜索跟踪参数(wpsrc和wpcn)。标签执行顺序至关重要,必须确保正确执行顺序。

本示例作为 临时解决方案 提供,用于启用自然搜索追踪功能。该代码仅供参考, 应由 网站开发人员根据营销部门需求进行更新维护。 自然搜索追踪 对不同广告商可能具有不同含义。 请 审阅示例并根据实际需求调整。

为何使用此方案?

  • 确保自然搜索访问被正确追踪, 即使未携带广告系列参数。

  • 在URL末尾添加Singular形式的"来源"参数wpsrc(取值自推荐来源),并添加"活动名称"参数wpcn (取值设为"OrganicSearch"), 实现清晰归因。

  • 将当前URL和引荐来源存储于localStorage 以备后续使用。

  • 纯 JavaScript 实现,零依赖,轻松集成。

工作原理

  1. 检查页面URL是否包含已知营销活动参数 (来自Google、Facebook、TikTok、UTM等)。

  2. 若未检测到营销参数且来源为搜索引擎,则追加:

    • wpsrc (将来源网址设为其值)
    • wpcn (值为OrganicSearch)
  3. 更新浏览器中的URL,无需重新加载页面。

  4. 将当前URL和引荐来源存储于localStorage 作为sng_urlsng_ref

使用方法

  1. 在GTM中导航至标签 > 新建,点击 标签配置,然后选择 自定义HTML
  2. 粘贴完整的JavaScript代码(如下所示)。
  3. 此标签无需创建触发器。 由于该标签必须在Singular初始化标签前执行, 我们将 通过标签序列功能配置其在初始化标签前触发。

    1. 使用描述性名称,例如"Singular - 自然搜索 配置"。
    2. 打开您的 Singular Web SDK 初始化标签。
    3. 点击标签配置
    4. 转至高级设置 > 标签序列化。
    5. 勾选"在[Singular初始化标签]触发前执行设置标签"。
    6. 从下拉菜单中选择您的"Singular - 自然搜索设置"标签 。
    7. 推荐操作:勾选"若[设置标签]失败则不触发[Singular初始化标签]",以防止URL修改不完整时的初始化。
    8. organic_search.png
    9. 保存标签。
  4. 使用GTM预览模式验证:

    • 设置标签首先触发并修改URL。
    • Singular初始化标签随后触发,读取 修改后的 URL参数。
    • 标签间无时间冲突。

关于高级标签排序:请参阅谷歌 官方 文档:


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>

跨子域跟踪

默认情况下,Singular网站SDK会生成Singular设备ID并通过浏览器存储进行持久化。由于该存储无法在子域名间共享,SDK最终会为每个子域名生成新ID。

若需跨子域持久化 Singular 设备 ID,可采用以下任一方案:

方法 B(高级):手动设置 Singular 设备 ID
#

方法 B(高级):手动设置 Singular 设备 ID

若不希望 Singular SDK 自动持久化设备 ID, 您可 通过跨域方式手动持久化 ID——例如使用 顶级域名 Cookie 或服务器端 Cookie。该值应为 Singular 先前 生成的 有效 uuid4 格式 ID。

注意:您可通过定义自定义 JavaScript 变量,并在调用 Init 跟踪类型标签后调用 singularSdk.getSingularDeviceId() 读取 Singular 设备 ID。

mceclip2.png


常见GTM实施问题

必需设备标识符
事件触发多次 优化或限制触发器,使用"每页仅触发一次"或添加条件
产品ID格式错误 产品ID必须采用反向DNS格式(com.company.site
事件未被追踪 检查事件名称拼写及大小写;确保singular SDK在事件标签触发前已加载
SDK脚本被拦截 广告拦截器或限制性内容安全策略;若问题持续,请考虑 迁移至原生JS实现方案

最佳实践

  • 保持GTM有序:为标签和触发器命名时清晰标注其用途并记录说明。
  • 定期审核闲置或遗留标签。
  • 尽量减少触发器数量以降低事件重复风险。
  • 变更后务必在GTM预览模式下全面测试, 再推送至生产环境。
  • 若使用基于Cookie的跟踪(跨子域跟踪), 请相应更新 隐私政策

相关文章