GA4 怎么统计在线客服带来的转化:事件代码和关键事件设置

二十行 JavaScript 就能把聊天事件发进 GA4。真正难的是判断哪个事件才算转化,哪个只是看起来像。

在 GA4 里统计在线客服带来的转化,做法是:监听聊天插件的 JavaScript 事件,用 gtag('event', …) 发到 GA4,再到管理 → 数据显示 → 事件里把该算转化的那个标成关键事件。代码部分大概二十行。难的是判断哪个事件才算转化——绝大多数独立站就是在这一步统计错的。

要点速览

  • 浏览器里能拿到两个信号:聊天窗口打开了、收到了一条回复。分别用 SealReply.on("open", …) 和 SealReply.on("message", …) 监听。
  • 发之前一定要去重。访客换一个页面时插件会把聊天窗口恢复成打开状态,open 会再触发一次,一个人能被算成三次转化。
  • 只有在「询盘主要靠聊天进来」的站上,chat_open 才值得标成关键事件,否则留着当普通事件用来做对比。
  • 浏览器永远看不到「访客真的发了一句话」,这个信号在你自己的服务器上,是 conversation.created 这个 Webhook。
  • 要算钱,别在 GA4 里硬凑:把订单回传给客服后台,报表 → 成果会直接告诉你「聊过天后下单」有多少单、多少钱。

哪个动作才算在线客服的转化?

在线客服的转化,指的是访客变成了一条你能跟进的询盘,而不是聊天窗口被打开的那一刻。打开聊天太便宜了:有人纯属好奇点一下,有人点开就走,有人来回点两次。把每次打开都标成关键事件,转化数会虚高,Google Ads 的智能出价也会跟着学歪。

所以写代码之前先想清楚你要量的是哪个信号。能拿到的信号一共这几个:

信号 在哪儿拿 适合当关键事件吗
聊天窗口被打开 浏览器,插件的 open 事件 只有询盘主要靠聊天进来的站适合
访客收到了一条回复 浏览器,插件的 message 事件 不适合,它量的是你团队的速度
访客发出了第一句话 服务器,conversation.created Webhook 适合,这才是真正的询盘
访客留下了邮箱或电话 服务器,visitor.identified Webhook 适合,这是一条线索
聊天之后下了单 服务器,订单回传接口 适合,但在客服后台看,别在 GA4 里看

A funnel showing visitors becoming chats and then orders

浏览器只知道前两行。这不是某一家插件的毛病,是所有聊天插件的共同边界:页面能看见窗口开没开、回复到没到,看不见访客打了什么字。所以大多数小站实际可行的方案是——浏览器端发 chat_open,把它当成一个参与度信号;等开了付费套餐,再让服务器把精确的转化补上。

第一步:先确认页面上已经有 GA4

打开自己的网站,按 F12,在控制台里敲 typeof gtag。返回 "function" 就可以往下做了。返回 "undefined",说明要么没装 GA4,要么是用 Google 跟踪代码管理器(GTM)装的,直接看文末的 GTM 的做法。

另外要准备好衡量 ID,就是 G- 开头加十位字符的那串,在管理 → 数据流 → 你的网站数据流里。用 gtag 直接装的话,页面上长这样:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { dataLayer.push(arguments); }
  gtag("js", new Date());
  gtag("config", "G-XXXXXXXXXX");
</script>

两个细节会影响下一步。一是 gtag 本质上是个队列,Google 的脚本还没加载完就调用它也没事,之后会补发;二是聊天插件的脚本和它互不相干,两段代码谁先谁后都行。

第二步:把聊天事件发到 GA4

把下面这段贴在两段代码后面、</body> 之前。它会等插件就绪,然后转发两个事件,并且保证一个浏览器会话里每个事件只发一次:

<script>
  function whenChatReady(fn) {
    if (window.SealReply) fn();
    else window.addEventListener("sealreply:ready", fn, { once: true });
  }

  // GA4 来一条算一条:同一个访客在四个页面上打开聊天,
  // 就是四次转化。所以每个事件一个会话只发一次。
  function sendOnce(name, params) {
    var key = "sr_ga4_" + name;
    if (sessionStorage.getItem(key)) return;
    sessionStorage.setItem(key, "1");
    gtag("event", name, params);
  }

  whenChatReady(function () {
    SealReply.on("open", function () {
      sendOnce("chat_open", { chat_page: location.pathname });
    });
    SealReply.on("message", function () {
      sendOnce("chat_reply_received", { chat_page: location.pathname });
    });
  });
</script>

三段各自在干什么:

  1. whenChatReady 解决的是 widget.js 带 async 加载的问题:你的脚本跑起来的时候,window.SealReply 可能还不存在。插件就绪时会在 window 上触发一个 sealreply:ready 事件,所以要么发现它已经加载好了,要么等这一下。不加这个判断,网速慢的时候监听没挂上、网速快的时候挂上了,或者反过来,而且不报错。
  2. sendOnce 把标记写进 sessionStorage,它跨页面跳转还在,关掉标签页或重开浏览器就没了。GA4 自己的会话是「30 分钟无活动就算结束」,两者对不齐,但实际用起来差别不大:访客明天再来,在两边都算新会话。
  3. chat_page 记录访客是从哪个页面打开聊天的。GA4 本来就给每个事件带 page_location,但自己定义的参数在探索报告里更好用,而且能直接看出是价格页还是产品页在制造疑问。

`open` 触发的次数比点击多

访客翻到下一页时,插件会把聊天窗口恢复成打开状态,这一下也会触发 open,没有任何点击。你自己的代码调 SealReply.open() 时同样会触发。所以 sendOnce 不是锦上添花:少了它,一个开着聊天看了两页的访客,就是两次 chat_open。

还有几条 Google 那边的硬限制,起名字之前先知道为好:事件名最长 40 个字符,一个事件最多带 25 个参数,参数值超过 100 个字符会被截断。这些写在 GA4 的资源项限制里,其中事件名超过 40 个字符的,根本不会被当成关键事件统计。

单页应用:监听只挂一次

用 React、Vue 或者 Next.js 的站,上面这段要放在根组件里跑一次,不要放在渲染「联系我们」按钮的那个页面组件里。前端路由跳转不会重新加载 widget.js,所以挂在页面组件里的监听会在每次切路由时再挂一遍,事件成倍往外发。要取消监听,就把函数存进变量,传给 SealReply.off("open", handler)。路由这块的细节在 Next.js 加聊天插件里写了。

第三步:把事件标成关键事件

GA4 里的关键事件就是以前说的「转化」:你标记为有价值的事件,之后才能进转化报表、受众群体和 Google Ads 出价。标记只要点四下,但 GA4 得先收到过这个事件。

  1. 自己先触发一次——在自己网站上把聊天打开。
  2. 进 GA4,点管理 → 数据显示 → 事件,chat_open 一天之内会出现在列表里。
  3. 打开它那一行的标记为关键事件开关。Google 的创建和修改事件这篇讲的是同一个开关。
  4. 默认关键事件价值先留空,除非你确实知道一次咨询平均值多少钱。随手填的数字会顺着所有带收入的报表一路扩散出去。

An event travelling from a web page into an analytics report

**别把 chat_reply_received 标成关键事件。**它是你团队回复时才触发的,意味着你回得快转化就涨、你去吃饭转化就跌——这是一个关于排班的指标,套着一个关于访客的指标的外衣。留着当普通事件,第五步会用上它。

第四步:验证事件真的发出去了

用报表 → 实时:在自己网站上打开聊天,等最多一分钟,在事件计数卡片里找 chat_open。实时报表看的是最近 30 分钟,而常规报表可能要等一天,所以测试只能在实时里测。

要看细节——参数到没到、值是什么——就开 DebugView。在页面网址后面加 ?debug_mode=1,或者临时在 gtag("config", …) 里加一个 debug_mode: true,然后一边点一边看管理 → DebugView。每个事件都会变成一张卡片,参数列在下面,几秒内就出现。

什么都没出现的时候

先看浏览器控制台有没有报错:你的监听函数抛异常会被插件吞掉(这样写错代码不会把聊天搞挂),代价就是出了错也静悄悄的。然后在那个具体页面上再确认一次 typeof gtag === "function"——有些建站平台的「统计」开关只在首页加载代码。

第五步:聊过天的访客,下单率更高吗?

chat_open 跑起来之后,就能回答你当初装这套东西想回答的问题了:聊过天的人,是不是比没聊过的更容易下单?用对比,别去分别看两张报表。

  1. 打开任意一张常规报表,比如报表 → 参与度 → 网页和屏幕。
  2. 点添加对比 → 新建,条件设成事件名称完全匹配 chat_open,范围选用户。
  3. 再加一个条件相反的对比,这样「聊过天」和「没聊过」就并排放着了。
  4. 把报表的指标换成 purchase,或者你站上本来就在发的那个关键事件。

结果要会读。会去点开聊天的访客,本来就比不点的更有意向——在聊天起作用之前,他已经走得更远了。这个对比能告诉你:愿意聊天的这拨人有多大、有多活跃,这个数字在你决定「要不要多排几个小时的班」时很有用。它不能证明是聊天带来了这些订单,而且 GA4 再怎么配也证明不了。

要算钱的话,把订单回传回来

把钱归到某次聊天上,是服务端的活,客服后台已经做好了。你的服务器把每笔订单发给订单接口,同一个访客在聊天后 30 天内下的单,会挂到他最近一次会话上:

POST https://api.sealreply.com/v1/site-orders
Authorization: Bearer sr_live_…
Content-Type: application/json

{
  "site_id": "0192f000-0000-7000-8000-000000000201",
  "order_id": "A-1001",
  "amount": "59.90",
  "currency": "USD",
  "external_id": "42"
}

external_id 是你自己网站的用户 ID,和传给插件的那个是同一个,所以这需要先打开签名识别——做法在在线客服怎么识别登录用户里。没登录就下单的游客,订单会存下来,但不会算成「聊过天后下单」。同一个 order_id 再发一次是更新(比如改金额),不会重复计数;订单取消或全额退款,用 DELETE /v1/site-orders/A-1001 撤回,报表里就不再算它。

然后报表 → 成果里会多一张卡片「聊过天后下单(网站回传)」,显示单数和各币种金额,就放在团队手动标记的成交旁边。卡片底下那句说明是实话:访客下单前 30 天内聊过天,不代表这笔收入是聊天带来的。

这部分要付费套餐

订单回传接口和 Webhook 需要入门版及以上;免费版没有开放 API,入门版每月 10,000 次调用。第一到第五步全在浏览器里跑,所有套餐都能用,免费版也行。

如果你非要在 GA4 里拿到那个精确的转化,服务端的路子是 Measurement Protocol:在浏览器里用 gtag("get", "G-XXXXXXXXXX", "client_id", cb) 取到访客的 GA4 客户端 ID,跟着访客一起存下来,等 conversation.created 这个 Webhook 到了,再带着这个 ID 往 Google 发一个 generate_lead 事件。收 Webhook 之前先验签,代码在 Webhook 签名怎么验证里。

哪些东西绝对不能发给 Google

聊天内容、邮箱、电话、姓名,一个都不要发进 GA4。Google 的条款禁止往 Analytics 里传能识别个人的信息,而聊天记录大概是你网站上最敏感的东西:订单号、收货地址、投诉,全在里面。事件参数是用来记「形状」的,不是记「内容」的——哪个页面、什么套餐档位、聊之前看了几页。

落到操作上就是一句话:参数只从你自己的页面结构里取,不要从聊天里取。chat_page: "/pricing" 没问题;first_message: "我的订单 A-1001 还没到" 就是一次数据泄露,只是绕了个弯。SealReply 这边,聊天内容是加密存储的,每个客户一把独立密钥,平台要查看须两人批准并通知你;把同样的文字抄一份送进第三方统计工具,前面这些就全白做了。

用 GTM 的话怎么做

如果 GA4 是通过 Google 跟踪代码管理器装的,结构不变,只改最后一行:不调 gtag,改成往数据层里推。

window.dataLayer = window.dataLayer || [];
dataLayer.push({ event: "chat_open", chat_page: location.pathname });

然后在 GTM 里:用一个自定义 HTML 代码,触发条件选初始化 - 所有页面,里面放监听代码;建一个自定义事件触发器匹配 chat_open;再建一个 GA4 事件代码把它发出去,chat_page 用数据层变量取。如果你改不了网站的 HTML,或者统计是别人在管、你只想给对方一个事件,走这条路更合适。

常见问题

不用 GTM 能在 GA4 里统计在线客服吗?

能。插件直接在页面上暴露了 SealReply.on("open", handler),所以一段调用 gtag("event", "chat_open") 的 <script> 就够了。只有在你本来就在用 GTM,或者改不了网站 HTML 的情况下,多加这一层才划算。

「打开聊天」该不该在 GA4 里标成转化?

只有在询盘主要靠聊天进来的站上才该标。打开聊天窗口只需要一次好奇的点击,如果你站上大部分人不问就直接买了,把它标成关键事件会让转化数虚高,还会把噪音喂给 Google Ads 出价。更稳的关键事件是「访客留下了联系方式」,它在服务器上是 visitor.identified 这个 Webhook。

chat_open 的数字为什么高得不正常?

基本都是没去重。访客翻到下一页时插件会把聊天窗口恢复成打开状态,open 会在没有点击的情况下再触发一次,而 GA4 收到一条就算一条。上面那个 sendOnce 把标记存在 sessionStorage 里,保证每个浏览器会话只发一次。

能看出一次咨询是哪个广告系列带来的吗?

GA4 里能:chat_open 和其他事件一样继承所在会话的来源、媒介和广告系列,所以在报表 → 获取客户 → 流量获取里加上聊天对比就能看。客服后台也能看,而且不用配任何东西——报表 → 来源分析里列了这些会话背后的来源渠道、广告系列、国家和设备。

加这段代码会拖慢网站吗?

影响小到测不出来。你加的是一个监听,外加每个会话最多两次 gtag 调用,页面上的重量还是原来那两段脚本。聊天插件本身约 38 KB 的异步脚本,不阻塞页面显示,GA4 的代码同样是异步加载的。

免费版能统计吗?

能,浏览器里的部分所有套餐都能用:插件的 JavaScript 接口、open 和 message 事件、GA4 的关键事件,都不限套餐。需要入门版及以上的是服务端那半边——Webhook、开放 API,以及把订单挂到会话上的订单回传。

接下来做什么?

先把统计做实,再想做花。把 chat_open 发出去,盯两周,看看到底有百分之几的访客会去点聊天;不少小站看到这个数字时,都会往某个方向惊讶一下。

如果你站上的聊天还是装完就没动过的默认状态,最影响这组数字的两个设置是「分页面的欢迎语」和「没人在线时的接法」,网站怎么添加在线客服里都写了;从安装到接待再到跟进成交的整条链路,在小网站在线客服完整指南里串了一遍。