概述
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,将导致追踪失败。
前提条件
开始前请确保您拥有:
-
SDK密钥与SDK密钥:
- 获取路径:登录Singular账户,依次进入开发者工具 > SDK集成 > SDK密钥。
-
产品ID:
-
定义:网站的唯一标识名,建议采用反向DNS格式(例如:
com.website-name)。 - 重要性说明:该ID将网站关联为Singular中的应用程序,必须与您在Singular应用页面中列出的Web应用程序bundleID完全一致。
-
定义:网站的唯一标识名,建议采用反向DNS格式(例如:
- 您的网站已配置Google Tag Manager。
- 需具备GTM中编辑网站标签、触发器及变量的权限。根据定制需求,可能需要掌握创建自定义标签及配置标签序列的方法。
- 若您无权限发布Google Tag Manager变更,需在测试后请他人代为发布。
- 掌握在GTM中预览和调试容器及标签的方法。
- 需列出待追踪事件清单。可参考我们的《Singular标准事件:完整列表》及《垂直领域推荐事件》获取灵感。
实施步骤
步骤1:将Singular网络追踪模板添加至GTM容器
- 登录您的Google Tag Manager账户并选择网站容器。
- 前往标签>新建。
- 为标签命名:"Singular初始化标签"
- 点击标签配置框开始设置
- 选择标签类型:点击"在社区模板库中探索更多标签类型"。
- 搜索"Singular"并选择"Singular Web Tracking"。点击"添加到工作区"按钮。
步骤 2:初始化 SDK
-
填写表单字段如下:
- 将跟踪类型设为初始化
- 在Api Key字段输入实际SDK密钥
- 在密钥字段输入实际SDK密钥 字段
-
输入实际产品ID。格式应为:
com.website-name,且需与Singular平台"应用程序"页面中的BundleID值一致。提示!测试时请使用专用产品ID
com.website-name.dev, 正式发布前请更新该ID。 此举可确保测试数据与生产环境数据 在Singular报告中完全分离。 -
可选:
- 日志级别:配置SDK 调试 日志输出至控制台。默认值为无。
- 事件去重
- 设置全局属性
- 启用智能横幅(仅限企业版)
- 设置客户用户ID
- 会话超时: 用户处于 非活动状态多长时间后 SDK将创建新会话。Singular 发送用户会话数据以计算用户留存率并启用 重新参与归因。默认值为30分钟。
- 跨子域名追踪
- 选择"触发器"配置触发条件以使该 标签 生效。
- 选择新建并命名触发器:"Singular 初始化触发器"。
- 点击触发器配置,选择"页面查看 - 窗口加载" 并点击"保存"。
- 再次点击"保存"以保存标签。
-
在标签页面点击"预览",测试
Singular
初始化标签是否被触发。
成功!若在预览控制台的"已触发标签"区域看到"Singular初始化标签",则表示初始化标签配置成功。
重要提示!对于 单页应用(SPA),每次跳转至新页面时 应触发 Page Visit跟踪类型。首次加载页面时 请勿 调用Page Visit,因初始化已报告 该页面 访问。
解决方案概述
- 使用自定义 JavaScript 变量检测是否为首次页面加载。
-
配置两个标签:
- "初始化标签"(追踪类型=初始化) 仅在首次页面加载时触发。
- "单次页面访问标签"(追踪类型 = 页面 访问)通过历史记录变更触发器,在每次路由变更时触发(初始加载除外)。
- 确保您的SPA在路由变更时将历史记录事件推送至dataLayer。
步骤3:事件追踪
初始化 SDK 后,当用户在网站执行重要操作时即可追踪自定义事件。
重要提示!Singular不会拦截重复事件! 开发者需自行添加保护措施,防止页面刷新或重复事件触发。建议针对收入事件采用去重方法,避免收入数据错误。具体示例请参见下文"步骤5:防止重复事件"。
基础事件追踪
通过有效JSON格式追踪基础事件或添加自定义属性, 为事件提供更多上下文信息:
-
使用Singular Web Tracking模板创建自定义事件标签。
-
命名事件标签。
-
选择跟踪类型 = 自定义事件
-
调整"事件名称"字段或设置相应变量。
-
调整"自定义用户ID"字段或设置相应变量。
-
根据需要调整"属性"以在事件中传递键值对。
-
配置触发器,确保标签仅在预期条件下触发。
-
保存触发器和标签,并在预览中测试。
转化事件追踪
跟踪转化事件:
-
使用Singular Web Tracking模板创建自定义事件标签。 为转化事件标签命名。
-
命名转化事件标签。
-
选择跟踪类型 = 转化事件
-
调整"事件名称"字段或设置相应变量。
-
调整"自定义用户ID"字段或设置相应变量。
-
根据需要调整"属性"以在事件中传递键值对。
-
配置触发器,确保标签仅在预期条件下触发。
-
保存触发器和标签,并在预览中测试。
收入事件追踪
通过收入信息追踪转化事件,并可添加可选属性获取更多上下文:
-
使用Singular Web Tracking模板创建自定义事件标签。 为收入事件标签命名。
-
为收入事件标签命名。
-
选择跟踪类型 = 收入事件
-
调整"事件名称"字段或设置相应变量。
-
调整"自定义用户ID"字段或设置相应变量。
-
调整"货币"字段或设置相应变量。 请以ISO 4217三字母货币代码传递收入货币, 例如"USD"、"EUR"或"INR"。
-
调整"收入"字段或设置相应变量。 收入必须以声明的货币单位表示。
-
如需传递事件的键值对,可调整"属性"字段。
-
配置触发器,确保标签仅在预期条件下触发。
-
保存触发器和标签,并在预览中测试。
常见事件实现模式
创建页面加载事件的GTM触发器
要通过Google Tag Manager实现Singular Web SDK,需创建页面加载触发器,在页面加载时触发。
快速设置:在GTM中导航至 触发器 新建 触发器配置,选择 "页面浏览"作为触发器类型。对于大多数实现场景, 选择"所有页面浏览"以在每次页面加载时触发。
完整设置指南:请参阅Google官方标签管理器文档:
为按钮点击创建GTM触发器
若需通过 Google 标签管理器使用 Singular Web SDK 追踪用户与按钮的交互, 需创建点击触发器, 当用户点击特定元素时触发。
快速设置:在GTM中导航至 触发器 新建 触发器配置,选择 "所有元素"以追踪任意页面元素(按钮、链接、图片)的点击, 或选择"仅链接"以仅追踪HTML锚点元素。
启用点击变量:创建点击触发器前, 请在GTM中启用内置点击变量: 依次进入变量 > 配置 > 内置变量, 勾选"点击"下的所有选项。
定位特定元素:为提升性能, 请使用"部分点击"替代"所有点击", 并添加基于点击ID、点击类或点击文本的条件 以定位特定按钮。
完整设置指南:请参阅Google官方标签管理器文档:
为表单提交事件创建GTM触发器
若要通过 Google Tag Manager 使用 Singular Web SDK 追踪表单提交,需创建表单提交触发器,当用户成功提交表单时触发。
快速设置:在GTM中导航至 触发器 新建 触发器配置,选择 "表单提交"作为触发器类型。启用 "检查验证"以确保仅对成功提交的表单进行追踪, 并建议启用 "等待标签"功能(延迟2000毫秒), 以便在页面重定向前完成追踪。
启用表单变量:创建表单触发器前, 请前往变量 > 配置 > 内置变量, 勾选"表单"下的所有选项以启用GTM内置表单变量。
性能优化:为提升性能, 请选择"部分表单"而非 "所有表单",并添加条件以定位 特定表单或存在表单提交的页面。
替代方案:需注意现代AJAX表单可能无法与标准表单提交触发器兼容。若内置触发器未生效,可考虑采用替代追踪方法,例如通过元素可见性触发器追踪成功提示或感谢页。
完整设置指南:请参阅谷歌官方标签管理器文档:
步骤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标签:
- 在您的 Google 标签管理器账户中,点击标签 > 新建。
- 在标签配置窗口中,点击 标签配置,并在标签类型 菜单中选择"Singular Web Tracking"。
- 在追踪类型下选择"登录"。
- 在自定义用户ID下,输入包含用户ID的Google Tag Manager变量。
- 点击触发器并添加触发事件:用户 登录 或注册。
- 点击保存。
若需清除用户ID,请添加具有 "注销"跟踪类型的标签:
- 在您的 Google 标签管理器账户中,点击标签 > 新建。
- 在标签配置窗口中,点击 标签配置,并在标签类型 菜单中选择"单一网页跟踪"。
- 在跟踪类型下选择"注销"。
- 点击触发器并添加触发事件:用户 注销。
- 点击保存。
注意事项:
- 用户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 初始化标签/模板中启用事件去重选项。
设置时间窗口(可选):配置判定两个事件为重复的最大时间窗口(以毫秒为单位,默认:1000毫秒/1秒)。
去重机制原理
启用后,当相同事件在时间窗口内再次发生时,SDK将抑制该事件(页面访问除外)。抑制依据为以下参数的哈希值:EventName 、EventProductName 、IsRevenueEvent 、CustomUserId 、GlobalProperties 、MatchId 、WebUrl(以及事件"额外"负载,如收入和自定义参数)。
步骤 6:测试 GTM 实施效果
- 开启GTM预览模式并加载您的网站。
- 检查以下事项:
-
SDK 是否加载成功(网络请求至
singular-sdk.js) - 事件触发符合预期且每项操作仅触发一次
-
浏览器控制台无与
singular相关的错误 -
网络请求是否发送至
sdk-api-v1.singular.net
- 使用浏览器开发者工具的"网络"选项卡验证 是否发送了正确的有效负载
成功!若在网络请求中看到正确的Singular事件且无重复项,即可上线!
步骤7:实现网页到应用的归因转发
网页到应用归因转发
使用Singular Web SDK追踪用户从网站到移动应用的旅程, 实现精准的网页广告活动归因至移动应用安装及 用户回访。 请按以下步骤配置网页到应用转发功能(含桌面端用户 二维码支持方案)。
- 请参照 《网站到移动应用归因转发指南》 配置 Singular Web SDK 以实现移动网页归因。
-
移动端网页到应用追踪操作:
-
添加"打开应用"标签,并将触发器设置为
按钮点击事件,用于打开或安装应用。
-
配置"打开应用"标签时, 请在Singular管理链接页面 指定您的移动网页到应用基础链接。
-
-
添加"打开应用"标签,并将触发器设置为
按钮点击事件,用于打开或安装应用。
-
针对桌面端网页到应用的跟踪:
-
创建二维码生成器清理标签
- 在GTM中导航至标签 > 新建, 选择 自定义HTML。
-
在代码区域添加:
- 注入您选择的二维码库
-
从以下链接获取网页到应用程序链接:
window.singularSdk.buildWebToAppLink(baselink); - 根据返回的链接生成二维码。
- 更新页面上的二维码图像。
- 标签命名:"单一 - QR码生成器 (清理版)"
- 无需触发器:清理标签继承 主标签的触发器
-
配置您的Singular初始化标签:
- 点击标签配置
- 前往高级设置 > 标签序列
- 勾选"在[Singular初始化标签]触发后执行清理标签"
- 从下拉菜单中选择您的二维码生成器标签
- 保存标签,并在预览模式下测试。
-
创建二维码生成器清理标签
提示!移动应用内浏览器网页视图(如 Facebook、Instagram 和TikTok所采用的)可能导致用户切换至设备原生浏览器时 改变 Singular设备ID,从而干扰归因。
为避免此问题,请始终为每个广告网络使用正确的Singular跟踪链接格式:
进阶主题
Singular横幅广告
添加全局属性
Singular SDK 允许您定义自定义属性,这些属性将随每次会话和事件一同发送至 Singular 服务器。这些属性可代表用户信息、应用模式/状态或其他任意自定义内容。
-
最多可定义5个全局属性,以有效JSON对象形式存储。全局属性将保存在浏览器的
localStorage中,直至该缓存被清除。 -
每个属性名称和值最多可包含200个字符。 若传递的属性名称或值超出此限制, 将被截断为200个字符。
-
全局属性将反映在Singular的用户级事件日志和回传数据中。
Google Tag Manager 现已在初始化阶段支持全局属性。在 Singular GTM初始化标签类型中,设置包含属性的 JSON 对象,并选择是否覆盖现有属性。
GTM中的全局属性标签类型
初始化时使用初始化标签类型设置全局属性。初始化后的运行时更新需使用专用全局属性标签类型(设置、获取、取消设置、清除)。
初始化标签配置
在SingularGTM初始化标签类型中,通过在Key和Value 字段中分配变量、数据层值或文本,设置Global Properties (对象)。根据需求调整Override (true/false)字段:
当Override 为false 时,现有属性保持不变;
当true 时,现有属性将被覆盖。
设置全局属性(初始化后)
使用"设置全局属性"标签类型在运行时添加或更新 单个属性。
Track Type: setGlobalProperties
propertyKey: user_type
value: premium
获取全局属性
使用"获取全局属性"标签类型读取 当前全局属性对象。可设置数据层 键名来存储全局属性对象。
Track Type: getGlobalProperties
取消设置全局属性
使用"清除全局属性"标签类型移除单个属性键(不清除整个对象)。
Track Type: unsetGlobalProperty
propertyKey: user_type
清除全局属性
使用"清除全局属性"标签类型移除 所有全局属性。
Track Type: clearGlobalProperties
自然搜索追踪
创建自然搜索追踪设置标签
重要提示! - 此标签必须在Singular SDK初始化前触发!
此自定义HTML标签会修改文档URL,添加Singular Web SDK在初始化期间需要读取的自然搜索跟踪参数(wpsrc和wpcn)。标签执行顺序至关重要,必须确保正确执行顺序。
本示例作为 临时解决方案 提供,用于启用自然搜索追踪功能。该代码仅供参考, 应由 网站开发人员根据营销部门需求进行更新维护。 自然搜索追踪 对不同广告商可能具有不同含义。 请 审阅示例并根据实际需求调整。
为何使用此方案?
-
确保自然搜索访问被正确追踪, 即使未携带广告系列参数。
-
在URL末尾添加Singular形式的"来源"参数
wpsrc(取值自推荐来源),并添加"活动名称"参数wpcn(取值设为"OrganicSearch"), 实现清晰归因。 -
将当前URL和引荐来源存储于
localStorage以备后续使用。 -
纯 JavaScript 实现,零依赖,轻松集成。
工作原理
-
检查页面URL是否包含已知营销活动参数 (来自Google、Facebook、TikTok、UTM等)。
-
若未检测到营销参数且来源为搜索引擎,则追加:
-
wpsrc(将来源网址设为其值) -
wpcn(值为OrganicSearch)
-
-
更新浏览器中的URL,无需重新加载页面。
-
将当前URL和引荐来源存储于
localStorage作为sng_url和sng_ref。
使用方法
- 在GTM中导航至标签 > 新建,点击 标签配置,然后选择 自定义HTML。
- 粘贴完整的JavaScript代码(如下所示)。
-
此标签无需创建触发器。 由于该标签必须在Singular初始化标签前执行, 我们将 通过标签序列功能配置其在初始化标签前触发。
- 使用描述性名称,例如"Singular - 自然搜索 配置"。
- 打开您的 Singular Web SDK 初始化标签。
- 点击标签配置
- 转至高级设置 > 标签序列化。
- 勾选"在[Singular初始化标签]触发前执行设置标签"。
- 从下拉菜单中选择您的"Singular - 自然搜索设置"标签 。
- 推荐操作:勾选"若[设置标签]失败则不触发[Singular初始化标签]",以防止URL修改不完整时的初始化。
- 保存标签。
-
使用GTM预览模式验证:
- 设置标签首先触发并修改URL。
- Singular初始化标签随后触发,读取 修改后的 URL参数。
- 标签间无时间冲突。
关于高级标签排序:请参阅谷歌 官方 文档:
<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
若不希望 Singular SDK 自动持久化设备 ID, 您可 通过跨域方式手动持久化 ID——例如使用 顶级域名 Cookie 或服务器端 Cookie。该值应为 Singular 先前 生成的 有效 uuid4 格式 ID。
注意:您可通过定义自定义 JavaScript 变量,并在调用 Init 跟踪类型标签后调用 singularSdk.getSingularDeviceId() 读取 Singular 设备 ID。
常见GTM实施问题
| 必需设备标识符 | |
|---|---|
| 事件触发多次 | 优化或限制触发器,使用"每页仅触发一次"或添加条件 |
| 产品ID格式错误 |
产品ID必须采用反向DNS格式(com.company.site )
|
| 事件未被追踪 |
检查事件名称拼写及大小写;确保singular SDK在事件标签触发前已加载
|
| SDK脚本被拦截 | 广告拦截器或限制性内容安全策略;若问题持续,请考虑 迁移至原生JS实现方案 |
最佳实践
- 保持GTM有序:为标签和触发器命名时清晰标注其用途并记录说明。
- 定期审核闲置或遗留标签。
- 尽量减少触发器数量以降低事件重复风险。
- 变更后务必在GTM预览模式下全面测试, 再推送至生产环境。
- 若使用基于Cookie的跟踪(跨子域跟踪), 请相应更新 隐私政策。