最终增强版 · fb_ads=1 · Session 兜底

WordPress 表单 Facebook 广告来源追踪指南

这是一个适用于 WordPress 自定义主题表单的简化追踪方案。用户从 Facebook / Meta 广告进入网站并提交表单后,询盘邮件中显示 Facebook Ads: Yes;非广告来源则显示 Facebook Ads: No

一、目标

这个方案的目标不是做复杂的广告归因,而是解决一个非常实际的问题:

在收到 WordPress 表单询盘邮件时,能够快速判断这个询盘是不是 Facebook 广告带来的。
简单 只需要一个 URL 参数:fb_ads=1
直观 邮件中直接显示:Facebook Ads: Yes/No
跨页面 用户从广告页跳转到联系页后仍然可以识别来源。

二、实现原理

广告落地页 URL 中添加 fb_ads=1。当用户访问带有这个参数的页面时,全站 JavaScript 会把广告来源状态保存到浏览器的 sessionStorage 中,并通过 PHP Session 做服务器端兜底。

1
点击 Facebook 广告
2
进入带 fb_ads=1 的页面
3
前端保存来源
4
用户跳转到表单页
5
邮件显示 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 输入框,只需要填写:

URL Parameters
fb_ads=1
不需要填写复杂的 UTM 参数。如果只想在邮件里判断是否来自 Facebook 广告,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 文件中。建议放在表单相关函数附近,或者文件底部。

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

启用 PHP Session
add_action('init', 'theme_start_session', 1);

function theme_start_session() {
    if (!session_id()) {
        session_start();
    }
}
服务器端记录 fb_ads=1
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,可以直接复用,无需重复添加 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">

表单示例

contact form
<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 里已经有类似代码,可以保留:

保留最终提交页面 URL
formData.push({ name: 'page_url', value: window.location.href });

Ajax 示例

jQuery 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_adsfirst_landing_pagefb_ads_landing_page,如果 JS 没有手动 append(),后台仍然收不到这些字段。

1. 手动 FormData 表单需要追加 Facebook Ads 字段

可以先在独立 JS 文件中增加一个读取追踪数据的方法:

读取 Facebook Ads 追踪数据
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 个字段:

手动 append 追踪字段
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 示例

evodek-sample-request.js
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>
上传新版 JS 后,建议检查页面源码中的 v= 是否变化,并使用无痕窗口测试。若网站启用了缓存插件或 CDN,也要清理对应缓存。

八、在 PHP 邮件函数中接收并输出字段

接收字段

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 更适合做广告后台转化归因、再营销和广告优化。

十一、后期添加到其他网站的步骤

  1. 在 Facebook / Meta 广告链接中添加 fb_ads=1
  2. 在目标 WordPress 主题的 functions.php 中加入全站追踪脚本。
  3. 在表单页面加入 3 个隐藏字段。
  4. 确认 Ajax 提交时包含 page_url
  5. 在 PHP 邮件函数中接收字段。
  6. 在邮件正文中输出 Facebook 来源信息。
  7. 如果表单使用独立 JS 且手动 new FormData(),记得把 Facebook Ads 字段手动 append() 进去。
  8. 修改独立 JS 后,更新版本号,例如 ?v=1.0.4,或使用 filemtime() 自动刷新版本。
  9. 使用无痕窗口进行测试。
复用到新网站时,只需要重点检查三处:广告链接是否带 fb_ads=1、表单是否有隐藏字段、邮件函数是否输出 Facebook Ads

十二补充:最终检查清单

  1. Facebook / Meta 广告链接是否带有 fb_ads=1
  2. 主题是否已经启用 PHP Session。
  3. functions.php 是否有服务器端兜底记录逻辑。
  4. 全站 wp_footer 脚本是否能正常输出。
  5. 表单隐藏字段是否存在,或者是否能自动注入。
  6. 所有邮件正文是否调用统一的 Facebook Ads 邮件输出函数。
  7. 普通 Ajax 表单是否使用 serializeArray()FormData(form)
  8. 手动 new FormData() 的表单是否手动 append 了 3 个字段。
  9. 独立 JS 更新后是否刷新版本号或使用 filemtime()
  10. 是否用无痕窗口分别测试广告流量和普通流量。
如果 functions.php 中包含 SMTP 邮箱、授权码或 API Key,不要上传到公开 GitHub 仓库。