W skrócie
- Pomiar ma cztery warstwy: AskSpot dostarcza warstwę 0 (widget) i 1 (zdarzenia); warstwy 2–4 (most do dataLayer, GTM, GA4) budujesz u siebie.
- Domyślnie ładuj widget przez GTM; dla dużych sklepów lepsza jest hybryda (widget z frontu, most z GTM).
conversationIdto klucz spinający wszystko – powstaje przy pierwszej wiadomości, nie przy otwarciu dymka.- Gotowy most do dataLayer (Custom HTML tag w GTM) uruchamia pomiar w kilka minut; pełny skrypt produkcyjny jest w uzupełnieniu referencyjnym.
- GA4 zawsze pokaże mniej rozmów niż panel AskSpot (zgody, adblocki) – to normalne, nie błąd.
Po co to robić – co realnie zmierzysz
Widget czatu AskSpot ma własny panel analityczny – widzisz w nim liczbę rozmów, kategorie pytań, rozwiązania. To jednak widok od strony czatu. Google Analytics daje widok od strony sesji użytkownika: skąd przyszedł, co oglądał przed rozmową, czy kupił po niej, ile wydał. Spinając jedno z drugim, odpowiadasz na pytania, na które osobno nie odpowiesz:
| Pytanie biznesowe | Co potrzebujesz |
|---|---|
| Jaki % ruchu w ogóle dotyka czatu? | askspot_widget_open / wszystkie sesje |
| Czy rozmawiający z czatem konwertują lepiej? | segment „sesje z askspot_conversation_started” vs reszta |
| Ile przychodu przechodzi przez ścieżkę z czatem? | purchase w segmencie z czatem |
| Na jakich podstronach czat jest najbardziej potrzebny? | page_location na askspot_conversation_started |
| Czy czat ratuje porzucone koszyki? | ścieżka askspot_* → begin_checkout → purchase |
| Które konkretne rozmowy skończyły się zakupem? | conversationId przekazany do purchase |
| Jak głębokie są rozmowy? | licznik new_user_action na rozmowę |
Architektura: cztery warstwy
Cały pomiar składa się z czterech warstw, jedna nad drugą:
| Warstwa | Co to jest | Kto ją buduje |
|---|---|---|
| 0 – ładowanie widgetu | Skrypt osadzający AskSpot (frontend / GTM / Custom Pixel) tworzy window.AskWidget[widgetId]. | AskSpot |
| 1 – API zdarzeń widgetu | widget.addEventListener("new_user_action", cb) i widget.sessionInfo.conversationId. | AskSpot |
| 2 – most do dataLayer | Twój skrypt mapuje zdarzenia widgetu na Twoje nazwy: window.dataLayer.push({ event: "askspot_...", ... }). | Ty |
| 3 – Google Tag Manager | Reguła Custom Event + zmienne Data Layer + tag GA4. | Ty |
| 4 – Google Analytics 4 | Zdarzenia + wymiary niestandardowe + kluczowe zdarzenia + eksploracje. | Ty |
Kluczowa zasada: AskSpot dostarcza wyłącznie warstwy 0 i 1. Warstwy 2–4 są po Twojej stronie – i to jest celowe. Nie narzucamy nazw zdarzeń, formatu payloadu ani konwencji. Dopasuj je do tego, co już masz w GA4.
Jeśli wolisz, żeby AskSpot zbudował za Ciebie warstwę 2 (most) wewnątrz naszego custom scriptu – to możliwe, ustalmy indywidualnie. Domyślnie zakładamy, że robisz to sam, bo masz kontrolę nad nazewnictwem.
Warstwa 0 – jak ładujesz widget
Skrypt osadzający wygląda tak (ID integracji dostajesz od nas – jest unikalne per widget):
<script
crossorigin="anonymous"
async
src="https://chat.askspot.io/api/v1/integration/{TWOJE_ID_INTEGRACJI}/embed-script"
></script>
Masz trzy sensowne miejsca, w których możesz go umieścić. Wybór ma realne konsekwencje analityczne.
Porównanie metod
| A. Bezpośrednio we frontendzie | B. Przez GTM (Custom HTML) | C. Shopify Custom Pixel | |
|---|---|---|---|
| Odporność na adblocki | ✓ najwyższa | ⚠ najniższa – adblocki blokują gtm.js w całości | ⚠ zależy od pixela |
| Obsługa zgód (consent) | ✗ robisz sam | ✓ wbudowana (Consent Mode / Cookiebot w GTM) | ✓ natywny consent API Shopify |
| Zasięg na ścieżce | tam, gdzie jest Twój kod | ✓ wszędzie, gdzie działa kontener | tylko tam, gdzie działa pixel |
| Działa na checkoucie Shopify | ✗ nie | ⚠ tylko przy Checkout Extensibility / Shopify Plus | ✓ tak (ale w sandboxie) |
| Widget się renderuje | ✓ tak | ✓ tak | ✗ nie (sandbox = brak dostępu do DOM strony) |
| Czas wdrożenia zmiany | deploy frontu | ✓ publikacja kontenera | publikacja pixela |
| Ryzyko duplikacji | – | ⚠ jeśli zostawisz też frontend | ⚠ jeśli zostawisz też frontend |
Rekomendacja
Domyślnie: GTM (wariant B). Powody:
- Zgody masz już rozwiązane – kontener GTM zwykle zna Cookiebot / CMP i respektuje Consent Mode. Nie duplikujesz logiki zgód w trzech miejscach.
- GTM działa na całej ścieżce, więc masz jedno miejsce prawdy.
- Zmiany bez deployu frontu.
Świadomy koszt: adblocki, które blokują googletagmanager.com, zablokują przy okazji czat. Na typowym e-commerce B2C to rząd kilku–kilkunastu procent ruchu. Jeśli czat jest krytycznym elementem obsługi klienta, a nie tylko „dodatkiem”, rozważ hybrydę.
Hybryda (zalecana dla dużych sklepów): widget ładowany bezpośrednio we frontendzie (odporność), most do dataLayer ładowany z GTM (zgody + elastyczność). Most i tak tylko nasłuchuje – jeśli GTM zostanie zablokowany, czat nadal działa, tracisz wyłącznie pomiar.
Nie duplikuj
Jeśli przechodzisz z frontendu na GTM – usuń skrypt z frontendu. Dwa równolegle załadowane embed-scripty to:
- dwa wpisy w
window.AskWidget→ most może podpiąć się do złego, - podwójne listenery → zdarzenia liczone dwa razy w GA4,
- potencjalnie dwa widgety w DOM.
Jeśli musisz mieć oba przejściowo, zabezpiecz się document.getElementById("askspot-script") przed dodaniem drugiego (zob. uzupełnienie referencyjne) i koniecznie użyj flagi window.askSpotDataLayerEventsBound, żeby most podpiął się tylko raz.
Ukrycie widgetu na wybranych podstronach
Na koszyku, checkoucie i stronie podziękowania widget zwykle przeszkadza. Nie rozwiązuj tego przez „nie ładuj skryptu” (stracisz pomiar) – poproś AskSpot o regułę wyświetlania po naszej stronie. Wtedy skrypt działa, analityka leci, a dymek się nie pokazuje.
Jeśli chcesz ukryć widget na konkretnych URL-ach, napisz do nas z listą wzorców – ustawimy to w konfiguracji integracji. Nie wymaga zmian w Twoim kodzie.
Warstwa 1 – API zdarzeń widgetu
Po załadowaniu skryptu widget wystawia globalne API:
window.AskWidget = {
"<widgetId>": {
sessionInfo: {
chatId: "...",
conversationId: "...", // null przed pierwszą wiadomością
sessionExpireTime: 1234567890,
isMinimized: false
},
addEventListener: (eventName, callback) => void,
openChat: () => void,
closeChat: () => void,
minimizeChat: () => void,
changeLabel: (label) => void
}
}
Jak się podpiąć
const widgetId = Object.keys(window.AskWidget)[0];
const widget = window.AskWidget[widgetId];
widget.addEventListener("new_chat_message", (event) => {
const conversationId = widget.sessionInfo?.conversationId;
console.log("AI odpisało w rozmowie", conversationId, event);
});
Cztery rzeczy, o których trzeba pamiętać
- Nazwy zdarzeń to stringi, nie stałe. Do
addEventListenerprzekazujesz wartość stringową ("showNotification"), a nie nazwę klucza z wewnętrznej enumeracji (add_notification). Trzymaj się dokładnie nazw z tabeli w sekcji „Katalog zdarzeń”. - Widget ładuje się asynchronicznie. Twój skrypt (zwłaszcza z GTM) prawie na pewno wystartuje, zanim
window.AskWidgetbędzie istniał. Potrzebujesz retry z interwałem – niesetTimeoutz jedną próbą. W przykładach używamysetInterval(500ms)z limitem 60 prób (30 sekund). To bezpieczne, bo zdarzenia i tak lecą dopiero po interakcji użytkownika. - Payload bywa stringiem. W niektórych ścieżkach (forwarding z iframe) payload przychodzi jako JSON string, a nie obiekt. Rozpakuj go defensywnie, zanim użyjesz (wzorzec poniżej).
conversationIdczytaj zwidget.sessionInfow momencie zdarzenia – nie cache’uj go przy podpinaniu listenera, bo wtedy jeszcze nie istnieje.
const data = typeof event?.data === "string"
? JSON.parse(event.data)
: event?.data;
Katalog zdarzeń widgetu
Zdarzenia publiczne (używaj tych)
| Zdarzenie | Kiedy leci | Payload | Do czego w analityce |
|---|---|---|---|
chat_loaded | Bundle czatu skończył się ładować po otwarciu | – | Diagnostyka wydajności |
chat_opened | Chat został otwarty | – | Zainteresowanie – górna część lejka czatu |
chat_closed | Chat zamknięty, stan rozmowy/UI posprzątany | – | Koniec interakcji |
minimize_chat | Chat zminimalizowany (nie zamknięty) | – | Odróżnia „odłożyłem” od „skończyłem” |
new_user_action | Użytkownik wysłał wiadomość / wykonał akcję | { actionName } | Rozpoczęcie i głębokość rozmowy |
new_chat_message | AI odpisało (nowa odpowiedź asystenta) | { awaitingUserResponse } | Liczba tur, responsywność |
session_update | Zmiana sesji | { chatId, conversationId, sessionExpireTime, isMinimized } | Źródło conversationId |
agent_mode_enabled | Tryb agenta (copilot) aktywowany | { source } / { widgetId } | Użycie zaawansowanych funkcji |
agent_mode_disabled | Tryb agenta wyłączony | { source, reason? } | j.w. |
showNotification | Pokazano proaktywną notyfikację / zaczepkę | { content, actions, durationMs } | Ekspozycja proaktywna – mianownik CTR |
hideNotification | Ukryto notyfikację | j.w. | – |
custom_open | Hook: jeśli masz listener, kliknięcie dymka odpala to zamiast domyślnego otwarcia | – | Własna logika otwierania |
Najczęściej wystarczy sześć: chat_opened, new_user_action, new_chat_message, minimize_chat, chat_closed, session_update.
Zdarzenia wewnętrzne (NIE podpinaj się)
Te istnieją w wewnętrznej magistrali zdarzeń widgetu, ale nie są częścią publicznego kontraktu. Mogą zniknąć lub zmienić semantykę bez ostrzeżenia:
open_chat, close_chat, add_button, remove_button, add_bar, remove_bar, iframe_style, update_label, minimize_notification, end_conversation, floating_button_animation_finished, setup, resize, run_agent, stop_agent, agent_steps_state
Do sterowania widgetem używaj metod publicznych: openChat(), closeChat(), minimizeChat(), changeLabel(...) – nie emituj wewnętrznych komend.
Zdarzenia biznesowe z konfiguracji rozmowy
Poza powyższymi widget może emitować własne zdarzenia zdefiniowane w konfiguracji Twojej integracji (conversationContext.eventAction) – np. „użytkownik kliknął rekomendowany produkt”, „przekierowanie do kategorii”. To najciekawsze zdarzenia z perspektywy sprzedażowej, ale są specyficzne dla Twojej konfiguracji.
Zapytaj swojego opiekuna AskSpot, jakie eventAction są aktywne na Twoim widgecie. To pytanie warto zadać, zanim zamkniesz zakres pomiaru.
conversationId – klucz spinający wszystko
To najważniejszy identyfikator w całym wdrożeniu. Jest to zarazem klucz, po którym łączysz swoje dane z analityką rozmów w panelu AskSpot.
Cykl życia
| Krok użytkownika | Zdarzenie | conversationId |
|---|---|---|
| Wchodzi na stronę | widget się ładuje | null |
| Klika dymek | chat_opened | null ⚠ |
| Wysyła PIERWSZĄ wiadomość | new_user_action → session_update | "abc-123" ✓ powstaje tutaj |
| Rozmowa trwa (kolejne wiadomości, nawigacja) | new_user_action / new_chat_message | "abc-123" (stały) |
| Kończy rozmowę i zaczyna nową | new_user_action | "def-456" (NOWY) |
Konsekwencje praktyczne
chat_openedichat_closedprzed pierwszą wiadomością nie mająconversationId. To nie błąd – rozmowa jeszcze nie istnieje. Nie próbuj tego łatać podstawianiemchatId; mieszanie przestrzeni identyfikatorów jest gorsze niż pusta wartość. Zdarzenia sprzed pierwszej wiadomości sklejaj po swoim identyfikatorze sesji (GA4client_id/session_id).conversationIdrotuje. Jeden użytkownik w jednej sesji może mieć kilkaconversationId. Jeśli liczysz „unikalnych użytkowników czatu”, licz poclient_id, nie poconversationId. Jeśli liczysz „rozmowy” – poconversationId.conversationIdprzeżywa nawigację między podstronami (jest w pamięci sesji widgetu), alesessionStorageTwojego mostu już niekoniecznie – dlatego deduplikacjaconversation_startedmusi trzymać listę widzianych ID wsessionStorage, nie w zmiennej JS. Bez tego każde przeładowanie strony w trakcie rozmowy zgłosi kolejny „start rozmowy” i zawyżysz metrykę.
Zapisz go jako User Property (opcjonalnie)
Jeśli chcesz mieć conversationId dostępny na każdym późniejszym zdarzeniu w sesji (w tym na purchase, którego nie kontrolujesz), ustaw go jako GA4 user property w momencie startu rozmowy. Szczegóły w uzupełnieniu o atrybucji.
Warstwa 2 – most do dataLayer (gotowy skrypt)
To Twój kod. Poniższy skrypt jest przykładem – nazwy askspot_* i zestaw parametrów dopasuj do swojej konwencji nazewniczej w GA4.
Wersja minimalna (start w 5 minut)
Wklej jako Custom HTML tag w GTM, wyzwalany na All Pages (albo Initialization – All Pages).
<script>
(function () {
window.dataLayer = window.dataLayer || [];
function init() {
var ids = window.AskWidget ? Object.keys(window.AskWidget) : [];
if (!ids.length) return false;
var widget = window.AskWidget[ids[0]];
function push(name) {
window.dataLayer.push({
event: name,
conversationId: widget.sessionInfo && widget.sessionInfo.conversationId
});
}
// dedup: ta sama rozmowa trwa przez wiele odsłon - start liczymy raz
function firstInConversation(id) {
if (!id) return false;
var key = "askspot_started_seen";
var list;
try { list = JSON.parse(sessionStorage.getItem(key)) || []; } catch (e) { list = []; }
if (list.indexOf(id) !== -1) return false;
list.push(id);
try { sessionStorage.setItem(key, JSON.stringify(list)); } catch (e) {}
return true;
}
widget.addEventListener("chat_opened", function () { push("askspot_widget_open"); });
widget.addEventListener("minimize_chat", function () { push("askspot_widget_minimize"); });
widget.addEventListener("chat_closed", function () { push("askspot_widget_close"); });
widget.addEventListener("new_user_action", function () {
var id = widget.sessionInfo && widget.sessionInfo.conversationId;
if (firstInConversation(id)) push("askspot_conversation_started");
});
return true;
}
// AskWidget ładuje się async - retry aż będzie dostępny (max 30 s)
if (!init()) {
var n = 0, t = setInterval(function () {
if (init() || ++n > 60) clearInterval(t);
}, 500);
}
})();
</script>
Efekt: do dataLayer trafiają askspot_widget_open, askspot_conversation_started, askspot_widget_minimize, askspot_widget_close – każde z conversationId.
Co dodać w wersji pełnej
Wersja minimalna nie mierzy głębokości rozmowy ani proaktywnych zaczepek. Pełny skrypt produkcyjny (w uzupełnieniu referencyjnym) dokłada:
askspot_user_messagez licznikiemmessageIndex– pozwala liczyć średnią długość rozmowy,askspot_ai_messagezawaitingUserResponse,askspot_notification_shown– mianownik do CTR zaczepek proaktywnych,askspot_agent_mode– użycie trybu agenta,- ochronę przed podwójnym bindowaniem (
window.askSpotDataLayerEventsBound), pageLocationna każdym zdarzeniu (przydatne, gdy GA4 przypisze zdarzenie do innej strony niż ta, na której faktycznie padło).
Konwencja nazewnicza – zrób to raz, porządnie
Zanim wkleisz skrypt, zdecyduj o nazwach. Zmiana po miesiącu = dziura w danych historycznych. Zalecany wzorzec: askspot_<obiekt>_<akcja>, snake_case, prefiks zawsze askspot_ (żeby jeden regex w GTM łapał wszystko).
| Zdarzenie widgetu | Sugerowana nazwa w dataLayer |
|---|---|
chat_opened | askspot_widget_open |
minimize_chat | askspot_widget_minimize |
chat_closed | askspot_widget_close |
pierwszy new_user_action w rozmowie | askspot_conversation_started |
każdy new_user_action | askspot_user_message |
new_chat_message | askspot_ai_message |
showNotification | askspot_notification_shown |
agent_mode_enabled | askspot_agent_mode |
Uwaga na limit GA4: darmowy GA4 ma limit 500 unikalnych nazw zdarzeń na property. Osiem nazw to nic, ale nie generuj nazw dynamicznie (np. askspot_msg_1, askspot_msg_2) – używaj parametrów.
Warstwa 3 – konfiguracja Google Tag Manager
Zmienne (Data Layer Variables)
Zmienne → Nowa → Data Layer Variable. Utwórz po jednej dla każdego parametru, którego chcesz używać:
| Nazwa zmiennej w GTM | Nazwa w Data Layer |
|---|---|
| DLV – conversationId | conversationId |
| DLV – messageIndex | messageIndex |
| DLV – actionName | actionName |
| DLV – awaitingUserResponse | awaitingUserResponse |
| DLV – pageLocation | pageLocation |
Ustaw Default Value na (not set) tam, gdzie parametr bywa pusty (np. conversationId przy askspot_widget_open). Inaczej GA4 dostanie undefined i parametr zniknie z raportu, zamiast pokazać wartość „brak”.
Reguła – jeden regex na wszystko
Reguły → Nowa → Zdarzenie niestandardowe (Custom Event):
- Nazwa zdarzenia:
^askspot_w+ - ✓ zaznacz Use regex matching
- Uruchamiaj: Wszystkie zdarzenia niestandardowe
Nazwij ją CE – AskSpot (all). Jedna reguła obsłuży wszystkie obecne i przyszłe zdarzenia z prefiksem askspot_. Jeśli chcesz osobno wyzwalać wybrane zdarzenia (np. tylko askspot_conversation_started jako konwersję), utwórz dodatkowe reguły z dokładnym dopasowaniem nazwy – ale tag zbiorczy i tak zostaw.
Tag GA4 Event
Tagi → Nowa → Google Analytics: zdarzenie GA4.
| Pole | Wartość |
|---|---|
| Tag konfiguracji / Measurement ID | Twój istniejący tag GA4 Configuration |
| Nazwa zdarzenia | {{Event}} |
| Reguła wyzwalająca | CE – AskSpot (all) |
Parametry zdarzenia:
| Nazwa parametru | Wartość |
|---|---|
conversation_id | {{DLV - conversationId}} |
message_index | {{DLV - messageIndex}} |
action_name | {{DLV - actionName}} |
page_location_custom | {{DLV - pageLocation}} |
Dlaczego {{Event}} jako nazwa? Bo jeden tag obsłuży wszystkie zdarzenia AskSpot. Alternatywa – osobny tag na każde zdarzenie – jest czytelniejsza w audycie, ale mnoży pracę. Przy 8 zdarzeniach zbiorczy tag wygrywa.
Ustawienia zgód na tagu
Na zakładce Zaawansowane → Ustawienia zgody ustaw dla tagu mostu (Custom HTML) i tagu GA4: Wymagana dodatkowa zgoda przed uruchomieniem: analytics_storage. Dzięki temu GTM sam wstrzyma tag do momentu uzyskania zgody, bez logiki w Twoim kodzie. Szczegóły w sekcji „Zgody”.
Kolejność ładowania
Jeśli ładujesz i widget, i most z GTM, ustaw:
- Tag AskSpot – embed script (Custom HTML) → trigger Initialization – All Pages
- Tag AskSpot – dataLayer bridge (Custom HTML) → trigger All Pages, w Zaawansowane → Kolejność tagów zaznacz „Uruchom tag przed tym tagiem”: AskSpot – embed script
W praktyce retry w moście i tak to załatwi, ale jawna kolejność skraca czas do pierwszego zdarzenia.
Warstwa 4 – Google Analytics 4
Same zdarzenia w GA4 to za mało. Bez poniższych kroków parametry nie pojawią się w raportach.
Zarejestruj wymiary niestandardowe
Administracja → Definicje niestandardowe → Utwórz wymiar niestandardowy:
| Nazwa wyświetlana | Zakres | Parametr zdarzenia |
|---|---|---|
| AskSpot Conversation ID | Event | conversation_id |
| AskSpot Message Index | Event | message_index |
| AskSpot Action Name | Event | action_name |
Wymiary działają tylko od momentu utworzenia. GA4 nie uzupełnia ich wstecz. Zarejestruj je tego samego dnia, w którym publikujesz kontener GTM – inaczej stracisz pierwsze dni danych. Limit: 50 wymiarów o zakresie Event w standardowym GA4. Sprawdź, ile masz wolnych, zanim dodasz trzy.
Oznacz kluczowe zdarzenia (key events)
Administracja → Kluczowe zdarzenia → Nowe kluczowe zdarzenie: askspot_conversation_started – to jest Twoja główna mikrokonwersja czatu. Nie oznaczaj jako key event askspot_user_message ani askspot_ai_message – będą się liczyć wielokrotnie w jednej sesji i zniekształcą współczynnik konwersji.
Odbiorcy (audiences) – fundament pod atrybucję
Administracja → Odbiorcy → Nowy odbiorca → Utwórz niestandardowego:
- Odbiorca „Użytkownicy czatu”: warunek – zdarzenie
askspot_conversation_startedwystąpiło co najmniej raz; okres członkostwa 30 dni. - Odbiorca „Otworzyli, nie napisali”: warunek –
askspot_widget_open≥ 1 ORAZaskspot_conversation_started= 0. Przydatne do oceny, czy dymek obiecuje coś, czego czat nie dowozi.
Odbiorcy zaczynają zbierać dane od momentu utworzenia – tak samo jak wymiary. Utwórz je od razu.
Raporty i eksploracje
A. Lejek czatu (Funnel exploration)
| Krok | Warunek |
|---|---|
| 1 | session_start |
| 2 | askspot_widget_open |
| 3 | askspot_conversation_started |
| 4 | add_to_cart |
| 5 | begin_checkout |
| 6 | purchase |
Ustaw lejek otwarty (open funnel), nie zamknięty – użytkownik może dodać do koszyka przed rozmową – i włącz podział wg Device category oraz Session default channel group.
Co z tego odczytasz: gdzie ludzie odpadają. Duży spadek między krokiem 2 a 3 = dymek przyciąga, ale pierwsza wiadomość jest za trudna (rozważ podpowiedzi startowe). Duży spadek między 3 a 4 = rozmowy nie prowadzą do produktu.
B. Porównanie konwersji: z czatem vs bez
Eksploracje → Eksploracja swobodna.
- Wiersze: Session default channel group
- Wartości: Sesje, Zakupy, Przychód, Współczynnik konwersji sesji
- Segmenty porównania: A = sesje zawierające
askspot_conversation_started; B = sesje nie zawierające
To nie jest dowód przyczynowości. Ludzie, którzy piszą do czatu, są z definicji bardziej zaangażowani – kupiliby częściej także bez czatu. Ta tabela pokazuje korelację i tak trzeba ją prezentować. Twardy dowód wymaga testu A/B (zob. uzupełnienie o atrybucji).
C. Gdzie zaczynają się rozmowy
Eksploracje → Eksploracja swobodna.
- Wiersze: Strona docelowa lub Page location
- Filtr: nazwa zdarzenia
askspot_conversation_started - Wartości: Liczba zdarzeń
Podstrony z największą liczbą startów rozmów to miejsca, gdzie treść nie odpowiada na pytania – bezpośredni sygnał dla treści i UX.
D. Głębokość rozmów
- Wiersze: AskSpot Message Index
- Wartości: Liczba zdarzeń
Rozkład pokazuje, na której wiadomości ludzie odpadają. Jeśli 70% rozmów kończy się na jednej wiadomości – albo czat odpowiada świetnie za pierwszym razem, albo tak źle, że użytkownik rezygnuje; zestaw to z danymi z panelu AskSpot (resolution), żeby rozstrzygnąć.
E. Ścieżka po rozmowie (Path exploration)
Eksploracje → Eksploracja ścieżek. Punkt początkowy = zdarzenie askspot_conversation_started; kroki +1, +2, +3 = nazwy zdarzeń. Pokazuje, co realnie robią użytkownicy zaraz po rozmowie. Jeśli dominuje page_view na kategorię, czat kieruje ruch; jeśli session_end – nie.
Eksport do BigQuery (dla zaawansowanych)
GA4 sampluje i agreguje. Jeśli chcesz policzyć realny wpływ czatu na przychód per rozmowa, podłącz BigQuery Export (darmowy w standardowym GA4) i połącz po conversation_id z eksportem rozmów z panelu AskSpot. To jedyny sposób na analizę bez ograniczeń kardynalności.
Atrybucja: czy czat sprzedaje? → gdy raporty są już gotowe, cztery poziomy atrybucji sprzedaży – plus pełne skrypty produkcyjne i słownik KPI – znajdziesz w uzupełnieniu zaawansowanym.
Zgody, Consent Mode, Cookiebot
Zasada
Czat AskSpot ma dwie natury i dwa reżimy zgody:
| Funkcja | Charakter | Zgoda |
|---|---|---|
| Sam widget czatu (obsługa klienta) | zwykle niezbędny funkcjonalnie | zwykle nie wymaga zgody analitycznej |
| Wysyłka zdarzeń do GA4 / dataLayer | analityczny | ✓ wymaga analytics_storage |
Cookie askspot_cid do atrybucji | analityczny | ✓ wymaga analytics_storage |
To kwalifikacja praktyczna, nie porada prawna. Ostateczną klasyfikację czatu w Twoim CMP ustal ze swoim inspektorem ochrony danych. Jeśli potrzebujesz, mamy gotowe zapisy do regulaminu i polityki prywatności dotyczące integracji czatu – poproś swojego opiekuna.
Rekomendacja: zostaw to GTM-owi
Najczystsze rozwiązanie: nie pisz logiki zgód w swoim kodzie. Zamiast tego:
- Upewnij się, że Twój CMP (Cookiebot, Consentmanager, OneTrust…) działa w trybie Google Consent Mode v2 i jest wdrożony w GTM.
- Na tagach AskSpot (most + GA4) ustaw Zaawansowane → Ustawienia zgody → Wymagana dodatkowa zgoda:
analytics_storage. - Gotowe. GTM sam wstrzyma tagi do momentu zgody i odpali je po jej udzieleniu (dzięki Consent Initialization).
Zalety: jedno miejsce zarządzania, spójność z resztą pomiaru, brak ryzyka rozjazdu, gdy CMP się zmieni.
Jeśli musisz sprawdzać zgodę w kodzie
Bywa konieczne – np. w Shopify Custom Pixel, gdzie GTM nie sięga. Wzorzec dla Cookiebota w sandboxie Shopify:
const getCookiebotConsent = async () => {
try {
const cookieString = await browser.cookie.get('CookieConsent');
if (!cookieString) return null;
return {
marketing: cookieString.includes('marketing:true'),
statistics: cookieString.includes('statistics:true'),
preferences: cookieString.includes('preferences:true')
};
} catch (e) {
return null;
}
};
async function loadIfAnalyticsConsentGranted() {
const consent = await getCookiebotConsent();
if (consent?.statistics) loadAskSpot();
}
Trzy pułapki tego podejścia:
- Parsowanie stringiem jest kruche.
includes('statistics:true')przestanie działać, jeśli Cookiebot zmieni format cookie. Traktuj to jako rozwiązanie tymczasowe. - Brak reakcji na zmianę zgody. Powyższy kod sprawdza zgodę raz. Jeśli użytkownik zaakceptuje ciasteczka po uruchomieniu kodu, nic się nie stanie. Dodaj nasłuch na zdarzenie zmiany zgody swojego CMP i ponów próbę.
- Domyślnie brak zgody = brak czatu. Jeśli sklasyfikowałeś czat jako niezbędny funkcjonalnie, blokowanie go pod
statisticsjest nadmiarowe – zablokuj wtedy tylko most, nie sam widget.
Consent Mode v2 – co się dzieje przy braku zgody
Przy Consent Mode v2 i odmowie zgody GA4 wysyła tzw. cookieless pings – bez identyfikatorów, ale zliczane w modelowaniu konwersji. Zdarzenia AskSpot nie zostaną wysłane, jeśli tag ma ustawione wymaganie analytics_storage. Efekt: Twoje liczby rozmów w GA4 będą niższe niż w panelu AskSpot. To oczekiwane i poprawne.
Na Shopify? Storefront działa dokładnie jak wyżej; checkout uruchamia się w izolowanym sandboxie Custom Pixela, z własnymi regułami. Zobacz analitykę AskSpot na Shopify.
Debugowanie i checklista QA
Sprawdź warstwę 1 (widget) – konsola przeglądarki
// 1. Czy widget w ogóle jest?
Object.keys(window.AskWidget || {});
// oczekiwane: ["<jakieś-id>"] - jeśli [] to skrypt się nie załadował
// 2. Podepnij logger na wszystko
(function () {
var w = window.AskWidget[Object.keys(window.AskWidget)[0]];
["chat_loaded","chat_opened","chat_closed","minimize_chat",
"new_user_action","new_chat_message","session_update",
"agent_mode_enabled","agent_mode_disabled","showNotification"]
.forEach(function (name) {
w.addEventListener(name, function (e) {
console.log("[AskSpot]", name, e, "cid:", w.sessionInfo && w.sessionInfo.conversationId);
});
});
console.log("logger podpięty");
})();
// 3. Sprawdź stan sesji
window.AskWidget[Object.keys(window.AskWidget)[0]].sessionInfo;
Napisz wiadomość do czatu i obserwuj konsolę. Powinieneś zobaczyć new_user_action → session_update (z conversationId) → new_chat_message.
Sprawdź warstwę 2 (dataLayer)
// podgląd na żywo wszystkich pushy z prefiksem askspot_
(function () {
var orig = window.dataLayer.push;
window.dataLayer.push = function () {
var a = arguments[0];
if (a && typeof a.event === "string" && a.event.indexOf("askspot_") === 0) {
console.log("[dataLayer]", a);
}
return orig.apply(window.dataLayer, arguments);
};
console.log("podsłuch dataLayer aktywny");
})();
// historia (co już poleciało)
window.dataLayer.filter(function (x) {
return x.event && String(x.event).indexOf("askspot_") === 0;
});
Sprawdź warstwy 3 i 4
- GTM Preview (Tag Assistant): włącz podgląd, przejdź ścieżkę, sprawdź, że w lewym panelu pojawiają się zdarzenia
askspot_*i że tag GA4 ma status Fired. Kliknij tag → Values → sprawdź, żeconversation_idnie jestundefined. - GA4 DebugView: Administracja → DebugView. Wymaga aktywnego GTM Preview albo rozszerzenia GA Debugger. Rozwiń zdarzenie i sprawdź parametry.
- GA4 Raporty czasu rzeczywistego: Raporty → Czas rzeczywisty → Liczba zdarzeń wg nazwy zdarzenia. Opóźnienie do ~1 min.
- Raporty standardowe: dane pojawią się po 24–48 h. Nie panikuj wcześniej.
Checklista przed publikacją
Warstwa 0 – ładowanie
- Skrypt AskSpot ładuje się dokładnie raz (Network → filtr
embed-script, jeden wpis). Object.keys(window.AskWidget).length === 1.- Po przejściu z frontendu na GTM: stary skrypt usunięty z motywu / kodu.
- Widget nie pokazuje się na koszyku / checkoucie / stronie podziękowania (reguła po stronie AskSpot).
Warstwy 1–2 – zdarzenia i most
askspot_widget_openleci przy otwarciu dymka.askspot_conversation_startedleci przy pierwszej wiadomości.askspot_conversation_startednie leci ponownie po przeładowaniu strony w trakcie rozmowy ← najczęstszy błąd.conversationIdjest niepuste naaskspot_conversation_started.- Most nie podpina listenerów dwa razy (
window.askSpotDataLayerEventsBound === true). - Zdarzenia lecą na wszystkich typach podstron (home, kategoria, produkt, koszyk).
Warstwy 3–4 – GTM i GA4
- Wszystkie zmienne DLV mają Default Value (
(not set)). - Trigger regex
^askspot_w+łapie wszystkie zdarzenia. - Tag GA4 ma ustawione
analytics_storagew Ustawieniach zgody; przy odmowie zgody tagi się nie odpalają (test w incognito). - Wymiary niestandardowe i kluczowe zdarzenie utworzone przed publikacją kontenera.
- Po 48 h liczby w GA4 są w tym samym rzędzie wielkości co w panelu AskSpot (rozjazd 10–40% w dół jest normalny).
Shopify – dodatkowo: Custom Pixel wdrożony najpierw na testowym sklepie; zweryfikowane, czy sandbox nie blokuje dostępu do pamięci rozmowy (czy conversationId jest dostępny na checkoucie); jeśli blokuje – włączony fallback na cookie askspot_cid na domenie głównej; zakup testowy łączy się z rozmową (potwierdzenie po stronie AskSpot – napisz do opiekuna z datą i godziną testu).
Weryfikacja po stronie AskSpot. Rozmowy i konwersje zapisujemy u siebie. Po Twoim teście możemy potwierdzić, czy zakupy poprawnie łączą się z rozmowami – nawet jeśli po Twojej stronie czegoś nie widać. Napisz do opiekuna z datą i przybliżoną godziną testu.
Najczęstsze błędy wdrożeniowe
- Zdublowany skrypt po migracji na GTM. Zostawiony skrypt we frontendzie + nowy w GTM = podwojone zdarzenia. Objaw: liczba rozmów w GA4 dwa razy większa niż w panelu. Zawsze usuwaj stary.
conversation_startedliczone przy każdej odsłonie. Brak deduplikacji poconversationIdwsessionStorage. Objaw: „starty rozmów” ≈ „wiadomości użytkownika”. Napraw funkcjąfirstInConversation.- Cache’owanie
conversationIdprzy podpinaniu listenera.const cid = widget.sessionInfo.conversationIdpoza callbackiem zwrócinullna zawsze. Czytaj wewnątrz callbacka. - Wymiary niestandardowe utworzone po fakcie. GA4 nie uzupełnia wstecz. Dwa tygodnie danych bez
conversation_idto dwa tygodnie stracone. - Brak Default Value w zmiennych DLV.
undefinedpowoduje, że parametr w ogóle nie trafia do GA4. - Jedna próba zamiast retry.
setTimeout(init, 1000)zadziała na szybkim łączu i zawiedzie na 3G. ZawszesetIntervalz limitem prób. - Sprawdzanie zgody tylko raz. Użytkownik akceptuje cookies po 3 s, kod sprawdził po 1 s → brak pomiaru dla całej grupy „akceptujących z opóźnieniem”. Nasłuchuj zmiany zgody.
- Traktowanie GA4 jako źródła prawdy o rozliczeniach. Zawsze będzie niższe. Do faktury służy panel AskSpot.
- Nazwy zdarzeń zmieniane w trakcie.
askspot_chat_start→askspot_conversation_startedpo miesiącu = dwa niepowiązane zestawy danych. Ustal konwencję przed startem. - Wyciąganie wniosków przyczynowych z segmentów. „Sesje z czatem konwertują 3× lepiej” nie znaczy „czat potraja konwersję”. Bez testu A/B mów o korelacji.
Pełne skrypty i słownik KPI → kompletny produkcyjny most do dataLayer, hook Reacta i słownik KPI znajdziesz w uzupełnieniu referencyjnym.
Czego ten poradnik nie rozstrzyga
Kilka rzeczy zależy od Twojej konkretnej instalacji i wymaga sprawdzenia u nas albo testu:
- Jakie zdarzenia biznesowe (
conversationContext.eventAction) są aktywne na Twoim widgecie – zapytaj opiekuna AskSpot. - Czy sandbox Shopify Custom Pixel przepuszcza dostęp do pamięci rozmowy – wymaga testu na Twojej instalacji.
- Dokumentacja API konwersji AskSpot (wariant server-side z Poziomu 3) – udostępniamy na życzenie.
- Reguły ukrywania widgetu na wybranych URL-ach – konfigurujemy po naszej stronie, przyślij listę wzorców.
- Kwalifikacja czatu w Twoim CMP (niezbędny funkcjonalnie vs analityczny) – decyzja Twojego IOD; mamy gotowe zapisy do polityki prywatności.
Chcesz zobaczyć, co dokładnie mierzysz? Zajrzyj na stronę Agenta czatu AI – a jeśli używasz też skrzynki, Agenta Skrzynki AI.
Dlaczego GA4 pokazuje mniej rozmów niż panel AskSpot?
Bo część ruchu odrzuca zgody analityczne, a adblocki blokują GTM/GA4. Przy Consent Mode v2 zdarzenia AskSpot nie lecą bez zgody na analytics_storage. Rozjazd 10–40% w dół jest normalny i poprawny – do rozliczeń służy panel AskSpot, nie GA4.
Kiedy powstaje conversationId?
Przy pierwszej wiadomości użytkownika (new_user_action), nie przy otwarciu dymka. chat_opened przed pierwszą wiadomością ma conversationId = null – to nie błąd, rozmowa jeszcze nie istnieje.
Czy czat na pewno podnosi konwersję?
Segment „sesje z czatem vs bez” pokazuje korelację, nie przyczynowość – rozmawiający są z definicji bardziej zaangażowani. Twardy dowód „czat dowozi X zł” daje wyłącznie test A/B ze stabilną grupą kontrolną.
Muszę pisać kod, czy AskSpot to zrobi?
AskSpot dostarcza warstwy 0 i 1 (widget + zdarzenia). Warstwy 2–4 (most, GTM, GA4) budujesz u siebie, żebyś kontrolował nazewnictwo. Jeśli wolisz, żebyśmy zbudowali most wewnątrz naszego custom scriptu – to możliwe, ustalmy indywidualnie.








