Audyt GA4 i GTM w sklepie internetowym zaczyna się od pytania, ile źródeł wysyła zdarzenie purchase i czy wszystkie tagi w ogóle mają szansę odpalić. W sklepie na PrestaShop, który przejęliśmy jako zewnętrzny dział IT, zakup trafiał do GA4 z trzech miejsc, baner zgody po cichu blokował część tagów, a Cloudflare trzymał stary kontener GTM nawet przez 31 dni. Poniżej opisujemy, jak to znaleźliśmy, co zmieniliśmy i dlaczego dziwne zdarzenia przypominające SQL injection okazały się ruchem skanera, a nie dziurą w sklepie.
Po czym poznać, że analityka sklepu wymaga audytu?
Właściciel zwykle widzi objaw, nie przyczynę. Liczba zamówień w GA4 nie zgadza się z panelem sklepu, kampania w Google Ads raportuje inną wartość niż analityka, a w raporcie zdarzeń pojawiają się nazwy, których nikt nie tworzył. Tak było i tutaj.
Zebraliśmy listę sygnałów, które w tym projekcie miały konkretną przyczynę:
- w raporcie zdarzeń GA4 nazwy wyglądające jak fragmenty zapytań SQL,
- Microsoft Clarity, Bing UET i remarketing Google Ads bez danych mimo zgody użytkowników,
- zmiany opublikowane w GTM widoczne u części odwiedzających dopiero po tygodniach,
- martwy hit Universal Analytics, który ładował bibliotekę analytics.js na każdej stronie,
- słaby wynik PageSpeed na karcie produktu, w którym duży udział miały skrypty Google.
Dlaczego GA4 liczy zakupy podwójnie?
Bo kilka mechanizmów wysyła to samo zdarzenie. W tym sklepie purchase pochodził z trzech źródeł: modułu GTM, drugiego modułu analitycznego oraz osobnego skryptu, który wysyłał dane zamówienia przez AJAX. Drugi moduł wysyłał zdarzenie przy każdym odświeżeniu strony podziękowania, bo nie zapamiętywał, że zamówienie zostało już zgłoszone. Moduł GTM miał deduplikację w bazie, ale z włączonym ponownym wysyłaniem przez 7 dni. W kontenerze tag GA4 purchase był podpięty pod dwa różne triggery.
Zanim cokolwiek usunęliśmy, policzyliśmy skalę. GA4 skleja zakupy o tym samym transaction_id, więc w raportach duplikaty stanowiły 1,4%. To niewiele, ale Google Ads i tagi remarketingowe nie zawsze stosują tę samą deduplikację, a trzy źródła oznaczają trzy miejsca, w których coś może się rozjechać przy aktualizacji modułu.
Zrobiliśmy dwie zmiany. Do szablonu drugiego modułu dodaliśmy zabezpieczenie oparte na cookie, takie samo, jakiego używa moduł płatności na swojej stronie potwierdzenia. W GTM tag purchase został podpięty pod jeden trigger.
Co pokazało zamówienie testowe?
Konfiguracja wyglądała poprawnie, więc złożyliśmy prawdziwe zamówienie z płatnością przelewem i obserwowaliśmy je w Tag Assistant. Zdarzenia begin_checkout i kroki checkoutu przeszły, ale na stronie podziękowania purchase się nie pojawił.
Przyczyna siedziała w nadpisanym kontrolerze potwierdzenia zamówienia. Wywoływał on hook potwierdzenia dwa razy, a za drugim razem wynik drugiego modułu ginął. Ten błąd istniał od dawna i był niewidoczny, bo zakupy opłacone przelewem, za pobraniem i na raty i tak docierały do GA4 przez trigger modułu GTM. Po poprawce override'u Tag Assistant pokazał na purchase tagi GA4, Google Ads i Bing UET.
Drugi test, z płatnością online przez bramkę płatniczą, pokazał, że po powrocie z bramki zdarzenie wysyła wyłącznie moduł GTM. Dlatego jego trigger został w kontenerze na stałe jako zabezpieczenie. Wniosek dla każdego sklepu: deduplikację trzeba sprawdzić osobno dla każdej metody płatności, bo każda kończy się inną ścieżką.
Czy zdarzenia z SQL injection w GA4 to atak na sklep?
W tym przypadku nie. Pierwsza hipoteza zakładała, że moduł GTM wpisuje surowy parametr controller z adresu URL do zmiennych dataLayer, więc spreparowany link trafia do GA4 jako nazwa kategorii strony. Parametr i tak oczyściliśmy w jednym miejscu modułu, bo to tania higiena. Dalsza analiza pokazała jednak, że dziwne nazwy zdarzeń nie przechodziły przez sklep.
GA4 przyjmuje dane na publicznym adresie zbierającym /g/collect. Identyfikator pomiaru jest widoczny w kodzie każdej strony, a Google nie wymaga przy tym żadnego uwierzytelnienia. Skaner podatności, który testuje wszystko, co widzi na stronie, wysłał więc żądania z payloadami SQL prosto do Google. W raportach wyglądało to groźnie, ale baza sklepu nie wykonała ani jednego z tych zapytań.
Jak to odróżnić od prawdziwego problemu:
- sprawdź, czy podejrzane wartości pojawiają się w logach serwera sklepu, czy tylko w GA4,
- przejrzyj dataLayer i zobacz, czy którakolwiek zmienna przepisuje parametry URL bez filtrowania,
- jeśli zdarzenia nie mają odpowiednika w ruchu na serwerze, to śmieci w analityce, a nie włamanie.
Takie wpisy wystarczy odfiltrować w raportach. Reakcja na incydent nie jest potrzebna.
Dlaczego baner CookieYes blokował tagi mimo zgody?
CookieYes działał w trybie defer, czyli ładował się po kontenerze GTM. Aktualizacja zgody przychodziła więc w momencie, gdy GTM już zdecydował, które tagi odpalić. GTM nie uruchamia wstecz tagów zablokowanych na starcie strony.
Skutek: tagi z dodatkowym warunkiem zgody, czyli Clarity, Bing UET i remarketing Google Ads, były blokowane u wszystkich użytkowników, również tych, którzy wcześniej zaakceptowali cookies. Bing UET nie zbierał danych od dnia wprowadzenia trybu zgody w kontenerze.
Poprawka polegała na tym, żeby w nagłówku strony, jeszcze przed GTM, odczytać zapisane cookie zgody i na jego podstawie ustawić consent default. Powracający użytkownik, który kiedyś kliknął „akceptuj”, ma wtedy właściwy stan zgody, zanim GTM zdąży zdecydować o pierwszym tagu, więc nic nie czeka na baner. Po wdrożeniu Tag Assistant pokazał przy Clarity, UET i remarketingu dynamicznym status „Powodzenie”.
Jak Cloudflare może cache'ować kontener GTM przez miesiąc?
Kontener ładował się przez Google Tag Gateway z domeny sklepu. Na tej ścieżce działała reguła Cloudflare „Cache Everything”, która nadpisywała nagłówek cache-control. Google ustawia dla kontenera 900 sekund, a Cloudflare zmieniał to na 31 dni i trzymał plik na serwerach brzegowych. Powracający klient mógł więc dostawać starą wersję kontenera nawet przez miesiąc po publikacji.
Wykluczyliśmy ścieżkę bramy z reguły cache. Odpowiedź ma teraz status BYPASS i max-age=900, czyli tyle, ile przewiduje Google.
Przy okazji okazało się, że kontener był pobierany dwa razy: raz przez auto-inject Cloudflare, raz przez własny loader w szablonie. Zdarzenia się nie dublowały, bo GTM inicjalizuje się tylko raz, ale każda strona płaciła za to 176 KB transferu i około 100 ms pracy procesora przed LCP. Własny loader usunęliśmy i zostawiliśmy wstrzykiwanie przez Cloudflare. Ma to swoją cenę: działanie GTM zależy teraz od ustawienia w panelu Cloudflare, co zapisaliśmy w dokumentacji sklepu.
Co jeszcze poprawiliśmy w kontenerze?
| Co zastaliśmy | Co zmieniliśmy |
|---|---|
Martwy gtag config Universal Analytics w szablonie modułu | Usunięta linia, analytics.js zniknął ze wszystkich stron |
| Skrypt motywu dołączony dwa razy, przez co add_to_cart wysyłał się wielokrotnie | Jeden include, jedno zdarzenie w GA4 |
| remove_from_cart nie wysyłał się przy przycisku „Usuń” | Push do dataLayer przed przeładowaniem koszyka |
Tag remarketingu z parametrem items zdefiniowanym 6 razy, wygrywał ostatni, nazwa zdarzenia pusta | Tag przepisany, 6 triggerów zamiast 14, items w formacie oczekiwanym przez Google Ads |
| Wyszukiwarka w nakładce zapisywała frazę w adresie, event search nie powstawał | Event search z nasłuchu zmiany adresu, w GA4 Realtime widać go od razu |
| Key events w GA4 odznaczone | Przywrócone 4, żeby agencja mogła je importować do Ads |
Każda zmiana w GTM szła jako nowa wersja kontenera, a przed pierwszą zrobiliśmy kopię wersji wyjściowej. Powrót do stanu sprzed audytu to jedno kliknięcie w historii wersji, a w sklepie odtworzenie plików z kopii.
Jak porządek w GTM wpłynął na PageSpeed?
Wynik PageSpeed Insights na mobile dla karty produktu wyraźnie wzrósł. Złożyło się na to kilka zmian naraz: loader GTM przez bramę first-party, Clarity i UET uruchamiane później, inline JavaScript przeniesiony do osobnego pliku z defer i poprawki LCP. Sama analityka nie odpowiada za cały wzrost.
PSI ma też dużą zmienność. W kolejnych przebiegach ta sama karta produktu dostawała wyniki różniące się o ponad 10 punktów, a pomiary uruchamiane równolegle wypadały gorzej. Dlatego w raportach dla klientów podajemy zakres, a nie jedną liczbę.
Na co uważać przy testach w Tag Assistant?
- Tag Assistant przypina wersję kontenera z chwili połączenia i wymusza ją we wszystkich kartach domeny. Po testach zamknij sesję.
- Zamówienie testowe trzeba potem anulować w panelu, żeby nie trafiło do raportów sprzedaży.
- Przekierowanie kategorii potrafi zgubić parametr omijający cache i pokazać starą wersję strony z Cloudflare.
- Każdą metodę płatności testujesz osobno, bo każda kończy się inną stroną.
Jak robimy audyt GA4 i GTM w ASAPDevs?
Zaczynamy od kopii kontenera i plików sklepu, potem spisujemy wszystkie źródła zdarzeń, a dopiero na końcu cokolwiek wyłączamy. Każdą poprawkę sprawdzamy na żywym zamówieniu w Tag Assistant i w GA4 Realtime. Pracujemy na PrestaShop i WooCommerce, a analitykę traktujemy jako część utrzymania sklepu, nie osobny projekt.
Liczby w GA4 nie zgadzają się z zamówieniami w panelu? Opisz, jakich modułów i bramek płatności używasz. Zobacz optymalizację PrestaShop → albo optymalizację WooCommerce →
