一、目标
这个方案的目标不是做复杂的广告归因,而是解决一个非常实际的问题:
fb_ads=1。
Facebook Ads: Yes/No。
二、实现原理
广告落地页 URL 中添加 fb_ads=1。当用户访问带有这个参数的页面时,全站 JavaScript 会把广告来源状态保存到浏览器的 sessionStorage 中,并通过 PHP Session 做服务器端兜底。
点击 Facebook 广告
进入带 fb_ads=1 的页面
前端保存来源
用户跳转到表单页
邮件显示 Yes
1. 用户点击 Facebook 广告
2. 进入 https://www.example.com/product-page/?fb_ads=1
3. 浏览其他页面
4. 进入 https://www.example.com/contact-us/
5. 提交表单
6. 邮件中显示 Facebook Ads: Yes
三、Meta / Facebook 广告参数设置
如果你的广告落地页是:
https://www.example.com/contact-us/
完整 URL 可以写成:
https://www.example.com/contact-us/?fb_ads=1
如果 Meta 广告后台有单独的 URL Parameters 输入框,只需要填写:
fb_ads=1
fb_ads=1 已经足够。
四、最终邮件显示效果
Facebook 广告来的询盘
Facebook Ads: Yes
First Landing Page: https://www.example.com/product-page/?fb_ads=1
Facebook Ads Landing Page: https://www.example.com/product-page/?fb_ads=1
Submitted Page URL: https://www.example.com/contact-us/
非 Facebook 广告来的询盘
Facebook Ads: No
First Landing Page: https://www.example.com/contact-us/
Facebook Ads Landing Page:
Submitted Page URL: https://www.example.com/contact-us/
五、在 functions.php 中加入全站追踪脚本
将以下代码添加到主题的 functions.php 文件中。建议放在表单相关函数附近,或者文件底部。
<?php
/**
* 全站记录 Facebook 广告来源
*
* 广告链接示例:
* https://www.example.com/contact-us/?fb_ads=1
*/
add_action('wp_footer', 'theme_output_fb_ads_tracking_script');
function theme_output_fb_ads_tracking_script() {
if (is_admin()) {
return;
}
?>
<script>
(function () {
var params = new URLSearchParams(window.location.search);
var hasFbAdsParam = params.get('fb_ads') === '1';
var currentUrl = window.location.href;
// 记录用户第一次进入网站的页面
if (!localStorage.getItem('first_landing_page')) {
localStorage.setItem('first_landing_page', currentUrl);
}
// 如果当前页面 URL 中带有 fb_ads=1,则标记为 Facebook 广告来源
if (hasFbAdsParam) {
localStorage.setItem('facebook_ads', 'Yes');
localStorage.setItem('fb_ads_landing_page', currentUrl);
} else if (!localStorage.getItem('facebook_ads')) {
localStorage.setItem('facebook_ads', 'No');
}
function setField(id, value) {
var field = document.getElementById(id);
if (field) {
field.value = value || '';
}
}
// 自动填充表单隐藏字段
setField('facebook_ads', localStorage.getItem('facebook_ads') || 'No');
setField('first_landing_page', localStorage.getItem('first_landing_page') || '');
setField('fb_ads_landing_page', localStorage.getItem('fb_ads_landing_page') || '');
})();
</script>
<?php
}
functions.php 中通过 wp_footer 全站输出。这样用户第一次进入产品页、首页或其他落地页时,都能立即记录 fb_ads=1。
五补充:服务器端 Session 兜底
除了前端 sessionStorage,也建议在 PHP 端记录一次来源。这样即使部分 Ajax 表单漏传隐藏字段,后台仍然可以尝试识别 fb_ads=1。
add_action('init', 'theme_start_session', 1);
function theme_start_session() {
if (!session_id()) {
session_start();
}
}
add_action('init', 'theme_capture_fb_ads_tracking_server_side', 2);
function theme_capture_fb_ads_tracking_server_side() {
if (is_admin()) {
return;
}
if (!isset($_SESSION) || !is_array($_SESSION)) {
return;
}
$current_url = esc_url_raw(home_url(add_query_arg(array(), $_SERVER['REQUEST_URI'] ?? '/')));
if (empty($_SESSION['theme_first_landing_page'])) {
$_SESSION['theme_first_landing_page'] = $current_url;
}
if (isset($_GET['fb_ads']) && sanitize_text_field(wp_unslash($_GET['fb_ads'])) === '1') {
$_SESSION['theme_facebook_ads'] = 'Yes';
$_SESSION['theme_fb_ads_landing_page'] = $current_url;
}
}
session_start()。
六、在表单页面加入隐藏字段
在表单提交按钮之前加入以下隐藏字段:
<input type="hidden" name="facebook_ads" id="facebook_ads" value="No">
<input type="hidden" name="first_landing_page" id="first_landing_page">
<input type="hidden" name="fb_ads_landing_page" id="fb_ads_landing_page">
表单示例
<form id="contactus" class="mb-4">
<!-- 其他表单字段 -->
<input type="hidden" name="facebook_ads" id="facebook_ads" value="No">
<input type="hidden" name="first_landing_page" id="first_landing_page">
<input type="hidden" name="fb_ads_landing_page" id="fb_ads_landing_page">
<button type="submit" class="btn btn-primary">Submit</button>
</form>
$form.serializeArray() 收集字段,新增的隐藏字段会自动随表单一起提交。
七、Ajax 提交时保留最终提交页面 URL
如果你的 Ajax 里已经有类似代码,可以保留:
formData.push({ name: 'page_url', value: window.location.href });
Ajax 示例
jQuery(function($){
$('#contactus').on('submit', function(e){
e.preventDefault();
var $form = $(this);
var $btn = $form.find('button[type="submit"]');
if ($btn.prop('disabled')) {
return;
}
var formData = $form.serializeArray();
formData.push({ name: 'action', value: 'send_contact_us_email' });
formData.push({ name: 'page_url', value: window.location.href });
$.ajax({
url: '<?php echo esc_url(admin_url('admin-ajax.php')); ?>',
type: 'POST',
dataType: 'json',
data: $.param(formData),
beforeSend: function(){
$btn.prop('disabled', true).text('Sending...');
},
success: function(res){
if (res.success) {
alert(res.data.msg || 'Submitted successfully.');
$form[0].reset();
} else {
alert(res.data && res.data.msg ? res.data.msg : 'Submission failed.');
}
},
error: function(){
alert('Network or server error, please try again later.');
},
complete: function(){
$btn.prop('disabled', false).text('Submit');
}
});
});
});
七补充:特殊表单与 JS 缓存处理
有些复杂表单不是直接提交整个 <form>,也不是使用 serializeArray(),而是在独立 JS 文件中手动创建 new FormData() 并逐个 append() 字段。免费样品页这类多步骤表单通常就是这种情况。
facebook_ads、first_landing_page、fb_ads_landing_page,如果 JS 没有手动 append(),后台仍然收不到这些字段。
1. 手动 FormData 表单需要追加 Facebook Ads 字段
可以先在独立 JS 文件中增加一个读取追踪数据的方法:
function getFbAdsTrackingData() {
function getFieldValue(fieldName) {
const field = document.querySelector(`[name="${fieldName}"], #${fieldName}`);
return field ? field.value : '';
}
const globalTracking = window.themeFbAdsTracking || window.evodekFbAdsTracking || {};
return {
facebook_ads:
getFieldValue('facebook_ads') ||
globalTracking.facebook_ads ||
sessionStorage.getItem('facebook_ads') ||
localStorage.getItem('facebook_ads') ||
'No',
first_landing_page:
getFieldValue('first_landing_page') ||
globalTracking.first_landing_page ||
sessionStorage.getItem('first_landing_page') ||
localStorage.getItem('first_landing_page') ||
window.location.href,
fb_ads_landing_page:
getFieldValue('fb_ads_landing_page') ||
globalTracking.fb_ads_landing_page ||
sessionStorage.getItem('fb_ads_landing_page') ||
localStorage.getItem('fb_ads_landing_page') ||
''
};
}
然后在构建 Ajax FormData 时追加这 3 个字段:
const fbAdsTracking = getFbAdsTrackingData();
formData.append('facebook_ads', fbAdsTracking.facebook_ads === 'Yes' ? 'Yes' : 'No');
formData.append('first_landing_page', fbAdsTracking.first_landing_page || '');
formData.append('fb_ads_landing_page', fbAdsTracking.fb_ads_landing_page || '');
2. 免费样品页 buildAjaxFormData 示例
function buildAjaxFormData(payload) {
const formData = new FormData();
formData.append('action', 'evodek_submit_sample_request');
formData.append('sample_request_nonce', ajaxNonce);
formData.append('first_name', payload.first_name || '');
formData.append('last_name', payload.last_name || '');
formData.append('phone', payload.phone || '');
formData.append('email', payload.email || '');
formData.append('message', payload.message || '');
formData.append('page_url', window.location.href);
formData.append('user_agent', navigator.userAgent || '');
const fbAdsTracking = getFbAdsTrackingData();
formData.append('facebook_ads', fbAdsTracking.facebook_ads === 'Yes' ? 'Yes' : 'No');
formData.append('first_landing_page', fbAdsTracking.first_landing_page || '');
formData.append('fb_ads_landing_page', fbAdsTracking.fb_ads_landing_page || '');
return formData;
}
3. 不同提交方式的处理区别
| 表单提交方式 | 是否自动带隐藏字段 | 是否需要手动 append |
|---|---|---|
| 普通表单提交 | 是 | 否 |
serializeArray() |
是 | 否 |
FormData(form) |
是 | 否 |
new FormData() 后逐个 append() |
否 | 是 |
4. 修改独立 JS 后要更新版本号,防止缓存
如果修改了 evodek-sample-request.js,浏览器、缓存插件或 CDN 可能还会加载旧文件。最简单的处理方式是修改 URL 后面的版本号:
<script src="<?php echo esc_url( get_theme_file_uri('js/evodek-sample-request.js?v=1.0.4') ); ?>"></script>
更推荐使用 filemtime(),每次 JS 文件更新后自动生成新版本号:
<?php
$sample_js_file = 'js/evodek-sample-request.js';
$sample_js_path = get_theme_file_path($sample_js_file);
$sample_js_ver = file_exists($sample_js_path) ? filemtime($sample_js_path) : '1.0.0';
?>
<script src="<?php echo esc_url( get_theme_file_uri($sample_js_file) . '?v=' . $sample_js_ver ); ?>"></script>
v= 是否变化,并使用无痕窗口测试。若网站启用了缓存插件或 CDN,也要清理对应缓存。
八、在 PHP 邮件函数中接收并输出字段
接收字段
$facebook_ads = sanitize_text_field($_POST['facebook_ads'] ?? 'No');
$first_landing_page = esc_url_raw($_POST['first_landing_page'] ?? '');
$fb_ads_landing_page = esc_url_raw($_POST['fb_ads_landing_page'] ?? '');
$page_url = esc_url_raw($_POST['page_url'] ?? '');
$facebook_ads = ($facebook_ads === 'Yes') ? 'Yes' : 'No';
加入邮件正文
$body .= ''
. '<br>'
. '<p><strong>Facebook Ads:</strong> ' . esc_html($facebook_ads) . '</p>'
. '<p><strong>First Landing Page:</strong> '
. ($first_landing_page ? '<a href="' . esc_url($first_landing_page) . '" target="_blank">' . esc_html($first_landing_page) . '</a>' : '')
. '</p>'
. '<p><strong>Facebook Ads Landing Page:</strong> '
. ($fb_ads_landing_page ? '<a href="' . esc_url($fb_ads_landing_page) . '" target="_blank">' . esc_html($fb_ads_landing_page) . '</a>' : '')
. '</p>'
. '<p><strong>Submitted Page URL:</strong> '
. ($page_url ? '<a href="' . esc_url($page_url) . '" target="_blank">' . esc_html($page_url) . '</a>' : '')
. '</p>';
推荐邮件字段结构
| 字段 | 含义 |
|---|---|
| Facebook Ads | 是否来自 Facebook 广告 |
| First Landing Page | 用户第一次进入网站的页面 |
| Facebook Ads Landing Page | 带有 fb_ads=1 的广告落地页 |
| Submitted Page URL | 用户最终提交表单的页面 |
| Submitted At | 表单提交时间 |
八补充:统一 PHP 邮件输出函数
建议把 Facebook Ads 邮件区块封装成一个统一函数,然后所有表单邮件都调用它。这样 Contact、购物车、计算器、免费样品页都能保持一致。
function theme_get_fb_ads_tracking_data_from_post() {
$posted_facebook_ads = sanitize_text_field($_POST['facebook_ads'] ?? 'No');
$posted_first_landing_page = esc_url_raw($_POST['first_landing_page'] ?? '');
$posted_fb_ads_landing_page = esc_url_raw($_POST['fb_ads_landing_page'] ?? '');
$posted_page_url = esc_url_raw($_POST['page_url'] ?? '');
$session_facebook_ads = sanitize_text_field($_SESSION['theme_facebook_ads'] ?? 'No');
$session_first_landing_page = esc_url_raw($_SESSION['theme_first_landing_page'] ?? '');
$session_fb_ads_landing_page = esc_url_raw($_SESSION['theme_fb_ads_landing_page'] ?? '');
$first_landing_page = $posted_first_landing_page ?: $session_first_landing_page;
$fb_ads_landing_page = $posted_fb_ads_landing_page ?: $session_fb_ads_landing_page;
$facebook_ads = ($posted_facebook_ads === 'Yes' || $session_facebook_ads === 'Yes') ? 'Yes' : 'No';
$combined_urls = $posted_page_url . ' ' . $first_landing_page . ' ' . $fb_ads_landing_page;
if ($facebook_ads !== 'Yes' && strpos($combined_urls, 'fb_ads=1') !== false) {
$facebook_ads = 'Yes';
if (!$fb_ads_landing_page) {
$fb_ads_landing_page = $posted_page_url ?: $first_landing_page;
}
}
return [
'facebook_ads' => $facebook_ads,
'first_landing_page' => $first_landing_page,
'fb_ads_landing_page' => $fb_ads_landing_page,
];
}
function theme_get_fb_ads_tracking_email_html() {
$tracking = theme_get_fb_ads_tracking_data_from_post();
return ''
. '<hr>'
. '<h3>Facebook Ads Tracking</h3>'
. '<p><strong>Facebook Ads:</strong> ' . esc_html($tracking['facebook_ads']) . '</p>'
. theme_render_email_url_line('First Landing Page', $tracking['first_landing_page'])
. theme_render_email_url_line('Facebook Ads Landing Page', $tracking['fb_ads_landing_page']);
}
邮件正文中只需要加入:
. theme_get_fb_ads_tracking_email_html()
九、测试方法
测试 1:直接在 Contact 页面提交
https://www.example.com/contact-us/?fb_ads=1
提交表单后,邮件中应该显示:
Facebook Ads: Yes
First Landing Page: https://www.example.com/contact-us/?fb_ads=1
Facebook Ads Landing Page: https://www.example.com/contact-us/?fb_ads=1
Submitted Page URL: https://www.example.com/contact-us/?fb_ads=1
测试 2:先进入产品页,再进入 Contact 页面
1. 打开 https://www.example.com/product-page/?fb_ads=1
2. 再进入 https://www.example.com/contact-us/
3. 提交表单
邮件中应该显示:
Facebook Ads: Yes
First Landing Page: https://www.example.com/product-page/?fb_ads=1
Facebook Ads Landing Page: https://www.example.com/product-page/?fb_ads=1
Submitted Page URL: https://www.example.com/contact-us/
测试 3:普通访问
https://www.example.com/contact-us/
邮件中应该显示:
Facebook Ads: No
localStorage,如果你之前访问过带 fb_ads=1 的链接,浏览器会一直记住 Facebook Ads: Yes。
清除测试记录
localStorage.removeItem('facebook_ads');
localStorage.removeItem('first_landing_page');
localStorage.removeItem('fb_ads_landing_page');
// 如果你的项目使用 sessionStorage,则执行:
sessionStorage.removeItem('facebook_ads');
sessionStorage.removeItem('first_landing_page');
sessionStorage.removeItem('fb_ads_landing_page');
十、常见问题
1. 只用 fb_ads=1,不用 UTM,可以吗?
可以。如果只是为了在邮箱里判断是否来自 Facebook 广告,fb_ads=1 已经足够。
2. 这个能区分具体广告系列、广告组、广告名称吗?
不能。这个方案只判断是否来自 Facebook 广告。如果后期需要区分具体广告系列,可以升级为 UTM 方案。
3. 用户跳转多个页面后还能识别吗?
可以。因为广告标记会保存到 localStorage。只要用户使用同一个浏览器,并且没有清除浏览器数据,后续进入 Contact 页面提交表单仍然可以识别。
4. 用户换浏览器或换设备后还能识别吗?
不能。localStorage 只保存在当前浏览器和当前设备中。
5. 这个能替代 Meta Pixel 吗?
不能完全替代。这个方案主要用于在询盘邮件中标记来源,方便人工查看。Meta Pixel 更适合做广告后台转化归因、再营销和广告优化。
十一、后期添加到其他网站的步骤
- 在 Facebook / Meta 广告链接中添加
fb_ads=1。 - 在目标 WordPress 主题的
functions.php中加入全站追踪脚本。 - 在表单页面加入 3 个隐藏字段。
- 确认 Ajax 提交时包含
page_url。 - 在 PHP 邮件函数中接收字段。
- 在邮件正文中输出 Facebook 来源信息。
- 如果表单使用独立 JS 且手动
new FormData(),记得把 Facebook Ads 字段手动append()进去。 - 修改独立 JS 后,更新版本号,例如
?v=1.0.4,或使用filemtime()自动刷新版本。 - 使用无痕窗口进行测试。
fb_ads=1、表单是否有隐藏字段、邮件函数是否输出 Facebook Ads。
十二补充:最终检查清单
- Facebook / Meta 广告链接是否带有
fb_ads=1。 - 主题是否已经启用 PHP Session。
functions.php是否有服务器端兜底记录逻辑。- 全站
wp_footer脚本是否能正常输出。 - 表单隐藏字段是否存在,或者是否能自动注入。
- 所有邮件正文是否调用统一的 Facebook Ads 邮件输出函数。
- 普通 Ajax 表单是否使用
serializeArray()或FormData(form)。 - 手动
new FormData()的表单是否手动 append 了 3 个字段。 - 独立 JS 更新后是否刷新版本号或使用
filemtime()。 - 是否用无痕窗口分别测试广告流量和普通流量。
functions.php 中包含 SMTP 邮箱、授权码或 API Key,不要上传到公开 GitHub 仓库。