Atrybucja czatu AskSpot w Google Analytics + skrypty referencyjne

To zaawansowane uzupełnienie głównego poradnika o nasłuchu czatu AskSpot w Google Analytics. Obejmuje cztery poziomy atrybucji sprzedaży oraz pełne skrypty referencyjne – produkcyjny most do dataLayer i hook Reacta – plus słownik KPI. Najpierw skonfiguruj podstawy w głównym poradniku GA4; tutaj wróć po głębię.

W skrócie

  • Trzy sposoby, by przypisać conversation_id do zdarzenia purchase: GA4 user property, sessionStorage albo cookie.
  • Atrybucja ma cztery poziomy – od segmentu sesji bez żadnej pracy po twardy test A/B.
  • Pełny produkcyjny most do dataLayer i hook Reacta są tutaj, gotowe do wklejenia.
  • Korelacja to nie przyczynowość – dopiero test A/B (poziom 4) dowodzi wpływu czatu na przychód.

Atrybucja: czy czat sprzedaje?

To pytanie, dla którego robi się całe wdrożenie. Są cztery poziomy – od najprostszego do najsolidniejszego.

Poziom 1: segment sesji (zero dodatkowej pracy)

Masz to od razu po eksploracji B w głównym poradniku. Porównujesz konwersję sesji z czatem i bez.

  • Plus: działa natychmiast, bez zmian w kodzie.
  • Minus: silny bias selekcji.

Nie mów „czat podnosi konwersję o X%” – mów „sesje z rozmową konwertują X razy częściej”.

Poziom 2: conversation_id na zdarzeniu purchase

Chcesz wiedzieć, która rozmowa skończyła się którym zamówieniem. Wymaga przekazania conversation_id do zdarzenia zakupowego, którego zwykle nie kontrolujesz bezpośrednio. Trzy sposoby:

Sposób A – GA4 User Property (najprostszy). W moście, przy starcie rozmowy, ustaw user property. W GTM, w Tag GA4 Event → Właściwości użytkownika, dodaj:

Właściwość użytkownikaWartość
last_conversation_id{{DLV - conversationId}}

Zarejestruj w GA4 wymiar niestandardowy o zakresie User dla last_conversation_id. Od tej pory każde zdarzenie tego użytkownika – w tym purchase – będzie miało ten wymiar.

  • Plus: jedna zmiana w GTM.
  • Minus: trzyma tylko ostatnią rozmowę; nie działa cross-device.

Sposób B – zmienna GTM czytająca sessionStorage. W moście (w funkcji push lub w listenerze session_update) zapisuj bieżące ID:

try { sessionStorage.setItem("askspot_current_conversation", id); } catch (e) {}

W GTM utwórz zmienną typu Custom JavaScript:

function () {
  try {
    return sessionStorage.getItem("askspot_current_conversation") || "(not set)";
  } catch (e) {
    return "(not set)";
  }
}

I dołóż ją jako parametr conversation_id do swojego istniejącego tagu purchase.

  • Plus: działa niezależnie od user properties, przeżywa nawigację.
  • Minus: sessionStorage nie przechodzi między domenami; na checkoucie w izolowanym sandboxie może być niedostępny.

Sposób C – cookie na domenie głównej (najbardziej odporny). Zamiast sessionStorage zapisz ID w cookie na domenie nadrzędnej: document.cookie = "askspot_cid=" + encodeURIComponent(id) + "; path=/; domain=.twojadomena.pl; max-age=1800; SameSite=Lax";

Cookie z domain=.twojadomena.pl jest widoczne na wszystkich subdomenach – w tym na checkoucie, jeśli działa on na subdomenie tej samej domeny głównej. W GTM odczytasz je zmienną typu First-Party Cookie.

  • Plus: działa cross-subdomain.
  • Minus: to cookie analityczne – musi podlegać zgodzie analytics_storage; zapisuj je tylko po uzyskaniu zgody i uwzględnij w polityce prywatności.

Poziom 3: konwersje po stronie AskSpot

Niezależnie od Twojego GA4, AskSpot może odbierać zdarzenia zakupowe i wiązać je z rozmowami po swojej stronie. Wtedy w panelu AskSpot widzisz przychód przypisany do rozmów, bez konfiguracji w GTM. Dwa warianty:

  • Wariant automatyczny. Nasz skrypt nasłuchuje zdarzeń zakupowych w Twoim środowisku (np. checkout_completed w Shopify Customer Events albo purchase w dataLayer), odczytuje conversationId z pamięci widgetu i wysyła konwersję – wartość + identyfikator rozmowy – bezpośrednio do naszego API. Nie musisz nic budować.
  • Wariant ręczny. Wysyłasz konwersje do naszego API sam, ze swojego backendu. To najsolidniejsza opcja, bo dane idą server-side (odporne na adblocki i brak zgód na cookies analityczne, o ile masz podstawę prawną). Poproś nas o dokumentację API konwersji.

Rekomendacja: zrób jedno i drugie. GA4 do analizy zachowań i porównań z resztą ruchu; API AskSpot jako niezależne, server-side źródło prawdy o przychodzie z rozmów. Rozjazd między nimi jest normalny i sam w sobie wiele mówi (pokazuje skalę utraty danych po stronie frontendu).

Poziom 4: test A/B (jedyny twardy dowód)

Wszystko powyżej mierzy korelację. Jeśli potrzebujesz liczby, którą można pokazać zarządowi jako „czat dowozi X zł”, potrzebujesz eksperymentu:

  • Podziel ruch: 90% z widgetem, 10% bez (grupa kontrolna),
  • przypisanie stabilne per użytkownik (cookie), nie per sesja,
  • minimum 2–4 tygodnie i wystarczająca próba,
  • porównaj przychód per użytkownik (nie konwersję sesji) między grupami.

Podział zrealizujesz w GTM (losowa zmienna + cookie) albo poproś nas o wsparcie po stronie konfiguracji integracji. Uwaga: wyłączanie czatu 10% klientów ma koszt obsługowy – to decyzja biznesowa, nie tylko analityczna.

Aneks A – pełny skrypt produkcyjny

Wersja rozszerzona: głębokość rozmowy, notyfikacje proaktywne, tryb agenta, ochrona przed podwójnym bindowaniem i opcjonalne cookie do atrybucji cross-subdomain.

<script>
(function () {
  "use strict";

  // ── KONFIGURACJA ──────────────────────────────────────────────
  var PREFIX       = "askspot_";
  var COOKIE_NAME  = "askspot_cid";
  var COOKIE_DOMAIN = "";        // np. ".twojadomena.pl" - puste = wyłączone
  var COOKIE_TTL   = 1800;       // sekundy
  var MAX_RETRIES  = 60;         // 60 × 500 ms = 30 s
  // ──────────────────────────────────────────────────────────────

  window.dataLayer = window.dataLayer || [];
  if (window.askSpotDataLayerEventsBound) return;   // już podpięte

  var messageCounters = {};      // conversationId -> licznik wiadomości usera

  function safeGet(key) {
    try { return sessionStorage.getItem(key); } catch (e) { return null; }
  }
  function safeSet(key, val) {
    try { sessionStorage.setItem(key, val); } catch (e) {}
  }

  function setConversationCookie(id) {
    if (!COOKIE_DOMAIN || !id) return;
    try {
      document.cookie = COOKIE_NAME + "=" + encodeURIComponent(id)
        + "; path=/; domain=" + COOKIE_DOMAIN
        + "; max-age=" + COOKIE_TTL + "; SameSite=Lax";
    } catch (e) {}
  }

  // dedup startu rozmowy - przeżywa nawigację między podstronami
  function firstInConversation(id) {
    if (!id) return false;
    var key = "askspot_started_seen";
    var list;
    try { list = JSON.parse(safeGet(key)) || []; } catch (e) { list = []; }
    if (list.indexOf(id) !== -1) return false;
    list.push(id);
    safeSet(key, JSON.stringify(list));
    return true;
  }

  function nextMessageIndex(id) {
    if (!id) return 1;
    var key = "askspot_msgcount_" + id;
    var n = parseInt(safeGet(key) || "0", 10) + 1;
    safeSet(key, String(n));
    messageCounters[id] = n;
    return n;
  }

  function unwrap(event) {
    if (!event) return {};
    var d = event.data !== undefined ? event.data : event;
    if (typeof d === "string") {
      try { return JSON.parse(d); } catch (e) { return { raw: d }; }
    }
    return d && typeof d === "object" ? d : {};
  }

  function init() {
    var ids = window.AskWidget ? Object.keys(window.AskWidget) : [];
    if (!ids.length) return false;

    var widget = window.AskWidget[ids[0]];
    if (!widget || typeof widget.addEventListener !== "function") return false;

    function cid() {
      return (widget.sessionInfo && widget.sessionInfo.conversationId) || "";
    }

    function push(name, extra) {
      var payload = {
        event: PREFIX + name,
        conversationId: cid(),
        pageLocation: location.href
      };
      if (extra) {
        for (var k in extra) {
          if (Object.prototype.hasOwnProperty.call(extra, k)) payload[k] = extra[k];
        }
      }
      window.dataLayer.push(payload);
    }

    // ── stan widgetu ──────────────────────────────────────────
    widget.addEventListener("chat_opened",   function () { push("widget_open"); });
    widget.addEventListener("minimize_chat", function () { push("widget_minimize"); });
    widget.addEventListener("chat_closed",   function () { push("widget_close"); });

    // ── wiadomość użytkownika ─────────────────────────────────
    widget.addEventListener("new_user_action", function (event) {
      var data = unwrap(event);
      var id   = cid();

      if (firstInConversation(id)) {
        setConversationCookie(id);
        push("conversation_started", { actionName: data.actionName || "" });
      }

      push("user_message", {
        actionName:   data.actionName || "",
        messageIndex: nextMessageIndex(id)
      });
    });

    // ── odpowiedź AI ──────────────────────────────────────────
    widget.addEventListener("new_chat_message", function (event) {
      var data = unwrap(event);
      push("ai_message", {
        awaitingUserResponse: data.awaitingUserResponse === true,
        messageIndex: messageCounters[cid()] || 0
      });
    });

    // ── sesja: odśwież cookie, gdy pojawi się / zmieni conversationId
    widget.addEventListener("session_update", function (event) {
      var data = unwrap(event);
      if (data.conversationId) setConversationCookie(data.conversationId);
    });

    // ── proaktywne notyfikacje (mianownik do CTR zaczepek) ────
    widget.addEventListener("showNotification", function (event) {
      var data = unwrap(event);
      push("notification_shown", {
        notificationContent: typeof data.content === "string"
          ? data.content.slice(0, 100)
          : ""
      });
    });

    // ── tryb agenta ───────────────────────────────────────────
    widget.addEventListener("agent_mode_enabled",  function () { push("agent_mode", { agentModeState: "enabled" }); });
    widget.addEventListener("agent_mode_disabled", function () { push("agent_mode", { agentModeState: "disabled" }); });

    window.askSpotDataLayerEventsBound = true;
    return true;
  }

  if (!init()) {
    var n = 0;
    var t = setInterval(function () {
      if (init() || ++n >= MAX_RETRIES) clearInterval(t);
    }, 500);
  }
})();
</script>

Zdarzenia produkowane przez ten skrypt:

Zdarzenie w dataLayerParametry
askspot_widget_openconversationId, pageLocation
askspot_widget_minimizeconversationId, pageLocation
askspot_widget_closeconversationId, pageLocation
askspot_conversation_started+ actionName
askspot_user_message+ actionName, messageIndex
askspot_ai_message+ awaitingUserResponse, messageIndex
askspot_notification_shown+ notificationContent (obcięte do 100 zn.)
askspot_agent_mode+ agentModeState

Uwaga o wolumenie. askspot_user_message i askspot_ai_message lecą przy każdej turze rozmowy. Przy dużym ruchu to znaczący wolumen zdarzeń w GA4 (limity kardynalności, koszt BigQuery). Jeśli nie potrzebujesz analizy głębokości rozmów, zakomentuj te dwa listenery – reszta działa niezależnie.

Aneks B – wersja React (hook)

Dla aplikacji Reactowych, gdzie ładujesz widget bezpośrednio z kodu (wariant frontendowy – zob. główny poradnik). Hook obsługuje ładowanie skryptu, retry bindowania i sprzątanie przy odmontowaniu.

import { useEffect } from "react";

const ASKSPOT_SCRIPT_ID = "askspot-script";
const ASKSPOT_SCRIPT_SRC =
  "https://chat.askspot.io/api/v1/integration/{TWOJE_ID_INTEGRACJI}/embed-script";

type AskSpotEventPayload = Record<string, unknown> | undefined;

type AskSpotWidget = {
  sessionInfo?: { conversationId?: string };
  addEventListener: (
    eventName: string,
    callback: (event?: AskSpotEventPayload) => void
  ) => void;
};

declare global {
  interface Window {
    AskWidget?: Record<string, AskSpotWidget>;
    dataLayer?: Record<string, unknown>[];
    askSpotDataLayerEventsBound?: boolean;
  }
}

const seenConversationIds = new Set<string>();

function markConversationStarted(conversationId: string) {
  if (seenConversationIds.has(conversationId)) return false;
  seenConversationIds.add(conversationId);
  return true;
}

function pushAskSpotEvent(
  eventName: string,
  widget: AskSpotWidget,
  payload?: AskSpotEventPayload
) {
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    event: eventName,
    conversationId: widget.sessionInfo?.conversationId,
    askspotPayload: payload,
  });
}

function bindAskSpotDataLayerEvents() {
  if (window.askSpotDataLayerEventsBound) return true;

  const widget = Object.values(window.AskWidget || {})[0];
  if (!widget) return false;

  widget.addEventListener("new_user_action", (event) => {
    const conversationId = widget.sessionInfo?.conversationId;
    pushAskSpotEvent("askspot_new_user_action", widget, event);
    if (!conversationId || !markConversationStarted(conversationId)) return;
    pushAskSpotEvent("askspot_conversation_started", widget, event);
  });

  widget.addEventListener("new_chat_message", (event) => {
    pushAskSpotEvent("askspot_new_chat_message", widget, event);
  });

  window.askSpotDataLayerEventsBound = true;
  return true;
}

function scheduleAskSpotDataLayerBinding() {
  let attempts = 0;
  let intervalId = 0;

  const tryBind = () => {
    if (bindAskSpotDataLayerEvents() || ++attempts >= 60) {
      window.clearInterval(intervalId);
    }
  };

  tryBind();
  intervalId = window.setInterval(tryBind, 500);
  return () => window.clearInterval(intervalId);
}

export function useAskSpotScript(enabled: boolean) {
  useEffect(() => {
    if (!enabled) return;

    if (document.getElementById(ASKSPOT_SCRIPT_ID)) {
      return scheduleAskSpotDataLayerBinding();
    }

    const script = document.createElement("script");
    script.id = ASKSPOT_SCRIPT_ID;
    script.async = true;
    script.crossOrigin = "anonymous";
    script.src = ASKSPOT_SCRIPT_SRC;

    let cleanupBinding: (() => void) | undefined;

    const handleScriptLoad = () => {
      cleanupBinding = scheduleAskSpotDataLayerBinding();
    };

    script.addEventListener("load", handleScriptLoad);
    document.head.appendChild(script);

    return () => {
      cleanupBinding?.();
      script.removeEventListener("load", handleScriptLoad);
    };
  }, [enabled]);
}

Użycie z obsługą zgody:

const analyticsConsent = useConsent("statistics");  // Twoja logika CMP
useAskSpotScript(analyticsConsent);

Różnice vs Aneks A:

Aneks A (vanilla/GTM)Aneks B (React)
Deduplikacja startusessionStorage – przeżywa przeładowanieSet w pamięci – ginie przy przeładowaniu
Licznik wiadomościtaknie
Cookie do atrybucjitak (opcjonalnie)nie
Ładowanie skryptuzakłada, że widget już jestładuje sam
Nazwy zdarzeńaskspot_user_message, askspot_ai_messageaskspot_new_user_action, askspot_new_chat_message

Ujednolić nazwy. Aneks B używa nazw kopiujących zdarzenia widgetu 1:1 (askspot_new_user_action), Aneks A – nazw opisowych (askspot_user_message). Wybierz jedną konwencję przed startem. Regex ^askspot_w+ w GTM złapie obie, ale w GA4 zobaczysz dwa niepowiązane zestawy zdarzeń, jeśli w trakcie zmienisz zdanie.

W SPA Set w pamięci wystarcza (nie ma pełnych przeładowań). Przy SSR / MPA przenieś deduplikację na sessionStorage – inaczej zawyżysz conversation_started.

Aneks C – słownik KPI

Metryki, które warto raportować, i sposób ich liczenia w GA4.

KPIDefinicjaJak policzyć
Zasięg czatu% sesji, w których dymek został otwartysesje z askspot_widget_open / wszystkie sesje
Współczynnik zagajenia% otwarć, które skończyły się pierwszą wiadomościąaskspot_conversation_started / askspot_widget_open
Penetracja rozmów% sesji z rozmowąsesje z askspot_conversation_started / wszystkie sesje
Średnia głębokość rozmowyile tur ma typowa rozmowaśrednia z message_index per conversation_id
CTR zaczepek proaktywnychskuteczność notyfikacjiaskspot_widget_open (po notification_shown) / askspot_notification_shown
Konwersja z czatemCR sesji z rozmowązakupy w segmencie „użytkownicy czatu” / sesje w tym segmencie
Uplift (korelacyjny)ile razy lepiej konwertują sesje z rozmowąCR z czatem / CR bez czatu – nie nazywaj tego wpływem czatu
Przychód wspomagany rozmowąprzychód w sesjach z rozmowąpurchase revenue w segmencie „użytkownicy czatu”
Przychód atrybuowanyprzychód powiązany z konkretnymi rozmowamipurchase z niepustym conversation_id
Uplift przyczynowyrealny wpływ czatuwyłącznie z testu A/B

Zalecany zestaw na start (5 metryk): zasięg czatu, penetracja rozmów, średnia głębokość, konwersja z czatem vs bez, przychód wspomagany. Reszta, gdy pierwsze pięć jest stabilne.

LinkedIn

Autor

Zespół AskSpot

AskSpot tworzy agentów AI – Chat i Inbox – dla e-commerce: doradztwo produktowe na stronie i obsługę 24/7 w mailu, czacie i na marketplace’ach, w ponad 200 językach.

Case studies

Realne wyniki sklepów e-commerce

Zobacz, jak sklepy internetowe wykorzystują AskSpot, by automatyzować rozmowy, odciążać obsługę i zwiększać sprzedaż.

Wszystkie case studies

Morele.net

Jak Morele.net obniżyło koszty obsługi klienta o 90%

Morele.net obniżyło koszty obsługi o 90%, z 50 do 5 osób. AskSpot rozwiązuje 65% z 37 600 rozmów miesięcznie bez konsultanta i odpowiada na Allegro.

Czytaj historię →

Militaria.pl

Jak Militaria rozwiązuje 72% czatów w 8 językach

Militaria.pl prowadzi czat AI AskSpot na trzech domenach i w 8 językach: 61% klikalności rekomendacji, 26% dodań do koszyka, 72% czatów bez konsultanta.

Czytaj historię →

4FIZJO

Jak 4FIZJO rozwiązuje 61% czatów klientów dzięki AI

Chat AI AskSpot rozwiązuje 61% z 21 360 rozmów miesięcznie bez konsultanta na 6 rynkach, a Agent Inbox sam obsługuje 25% zgłoszeń e-mail i Allegro w helpdesku Thulium.

Czytaj historię →

Meblobranie

Jak doradztwo w czacie wprowadza do koszyka meble ogrodowe za 8,2 mln zł miesięcznie

Agent AskSpot w Meblobranie odpowiada na pytania, od których zależy zakup mebli ogrodowych: wymiar, kolor, kotwienie, moskitiery. Doradztwo w czacie wprowadza do koszyka 8,2 mln zł miesięcznie, 20% czatów napełnia koszyk, 70% rozwiązanych.

Czytaj historię →

Szopex

Jak jeden agent AI obsługuje trzy marki Szopexu

Jeden agent AskSpot działa w trzech sklepach Szopexu – Sklep Biegacza, Warsaw Sneaker Store i SKStore – na trzech platformach e-commerce. 67% rozmów rozwiązanych, 58% CTR, 11,7% kończy się zakupem.

Czytaj historię →

moderno.dk

Jak moderno.dk rozwiązuje 64% czatów o meble bez konsultanta

Agent AskSpot rozwiązuje w moderno.dk 64% pytań o meble bez konsultanta – terminy dostaw, wymiary, dostępność – a 12,3% czatów kończy się koszykiem.

Czytaj historię →

16analiz przypadków

Zobacz wszystkie historie

Każdy sklep, który zmierzyliśmy – o co pytali klienci, co zrobił agent i jakie liczby z tego wyszły.

Przejrzyj wszystkie →

Gotowy, by każda rozmowa sprzedawała?

Umów demo i zobacz AskSpot na swoim asortymencie. Wdrożenie zajmuje kilka dni.