GA4 gubił połowę zakupów przez redirect bramki w Shoperze - capture & replay
Sklep sprzedaje, a GA4 widzi o połowę mniej zakupów. Winny redirect do bramki płatności ucinający event purchase. Naprawa wzorcem capture & replay.
Wyobraź sobie: sklep sprzedaje normalnie, zamówienia wpadają, kurierzy jeżdżą, pieniądze są na koncie - a GA4 pokazuje mniej więcej o połowę mniej zakupów, niż faktycznie zrobiłeś. Reklamy nagle wyglądają na nierentowne, bo połowa sprzedaży, którą napędziły, po prostu nie istnieje w danych. Budżet tniesz na podstawie obrazu, w którym brakuje co drugiej transakcji. Brzmi znajomo?
Tak właśnie zaczęło się to zgłoszenie - niewinnie: “Google Ads przekłamuje konwersje względem GA4, popraw atrybucję”. Klasyka, na pierwszy rzut oka temat na pół godziny. Tyle że im głębiej szedłem, tym mniej chodziło o Google Ads, a coraz bardziej o coś dużo gorszego: GA4 łapał ledwie część realnej sprzedaży sklepu - mniej więcej połowę zakupów po prostu nie widział.
To nie jest “rozjazd atrybucji”. To dziura, przez którą codziennie wyciekała połowa danych konwersji - i to w sklepie z nowoczesnym, poprawnie zbudowanym stackiem: web GTM, server-side GTM na własnej subdomenie, GA4, Google Ads, Meta CAPI, Consent Mode v2. Wszystko, co powinno działać, działało. A mimo to połowa zakupów nie istniała w danych.
Ten artykuł to war story z tej naprawy. Pokazuję metodę diagnozy (triangulacja i lejek), eliminację fałszywych tropów - bo połowa roboty to udowodnienie, czego problemem NIE jest - oraz rozwiązanie wzorcem capture & replay, które odzyskało gubione konwersje. Jeśli Twoje GA4 pokazuje mniej zakupów niż panel sklepu, jest duża szansa, że winowajca jest dokładnie ten sam - i prawie nikt o nim nie mówi.
Dlaczego nie zaczyna się od porównania Google Ads z GA4
Pierwszy odruch przy “Google Ads przekłamuje względem GA4” to otworzyć oba panele i je porównać. To błąd metodyczny. Oba narzędzia mogą kłamać, każde w inną stronę - Google Ads liczy po modelu atrybucji i oknie konwersji, GA4 po sesjach i swoim własnym modelu, a żadne z nich nie wie, ile realnie sprzedał sklep.
Punktem wyjścia musi być niezależne źródło prawdy. W e-commerce to realne zamówienia z panelu sklepu lub ERP - liczba transakcji, która rzeczywiście weszła do systemu i za którą ktoś zapłacił. Dopiero mając ten kotwiczny licznik, porównujesz z nim każde narzędzie z osobna.
Triangulacja trzech źródeł za ten sam okres wyglądała tak:
- Realne zamówienia (panel sklepu / ERP): pełna liczba transakcji - kotwica, źródło prawdy.
- GA4
purchase: około 37% tej liczby - czyli ledwie ponad jedna trzecia realnej sprzedaży. - Google Ads: gdzieś pomiędzy, z własnym przekłamaniem atrybucji.
W tym momencie temat “Google Ads vs GA4” przestał być problemem. Problemem stało się pytanie: gdzie ginie prawie dwie trzecie zakupów między kasą sklepu a GA4?
Diagnoza pomiaru zaczyna się od triangulacji z niezależnym źródłem prawdy, nie od porównywania dwóch narzędzi reklamowych między sobą. Oba mogą kłamać - tylko panel zamówień nie ma w tym interesu.
Eliminacja fałszywych tropów
Zanim doszło do prawdziwej przyczyny, trzeba było zdjąć ze stołu trzy “oczywiste” winy. Każdą z nich podejrzewałby każdy, kto zna server-side. I każda okazała się ślepym zaułkiem - ale dopiero po sprawdzeniu, nie po założeniu.
To nie był consent
Pierwsze podejrzenie przy stracie 60% danych: ludzie odrzucają zgody w Cookiebot i tracking się nie odpala. Logiczne, ale dane mówiły co innego.
Opt-in w panelu CMP wynosił około 70%. Ale wśród kupujących opt-in był praktycznie stuprocentowy. Skąd ta różnica? Survivorship bias. Po stronie serwera (dane Stape) widać było tylko tych użytkowników, którzy w ogóle wygenerowali zdarzenia - a kto odrzucił zgodę, ten zdarzeń nie generuje i w widoku serwerowym po prostu nie istnieje. W efekcie wyglądało to na 99% zgody, bo patrzyliśmy wyłącznie na ocalałych.
Gdyby consent gubił 60% zakupów, strata rozłożyłaby się równomiernie na cały lejek. A jak się za chwilę okaże, nie rozłożyła. Consent odpadł.
To nie było podwójne liczenie
Drugie podejrzenie idzie zwykle w drugą stronę: skoro czegoś brakuje, może gdzie indziej liczy się podwójnie i obraz jest zaburzony. W duplikacji eventów purchase chodzi o nadmiar, tu sprawdzałem, czy deduplikacja nie ucina za dużo.
Deduplikacja po transaction_id działała poprawnie - liczba zdarzeń purchase w przybliżeniu równała się liczbie transakcji w GA4. Nic nie było ucinane nadmiarowo, nic nie liczyło się podwójnie. Sam tag purchase też był skonfigurowany bez zarzutu i odpalał poprawne dane.
Innymi słowy: wszystkie podejrzane elementy działały. A mimo to dwie trzecie zakupów nie docierało. To jest moment, w którym przestaje się zgadywać i otwiera lejek.
Diagnoza przez lejek
Jeśli zakupy giną, ale nie wiadomo gdzie, jedyna sensowna metoda to rozłożyć ścieżkę zakupową na zdarzenia i policzyć, na którym kroku znika sygnał. Porównanie poszczególnych etapów lejka z liczbą realnych zamówień pokazało dokładnie, gdzie jest dziura.
Obraz lejka względem realnych zamówień:
| Zdarzenie | Pokrycie realnej sprzedaży |
|---|---|
begin_checkout | około 80% |
add_payment_info | (pełne dla docierających) |
purchase | około 37% |
begin_checkout łapał 80% zamówień - czyli sama górna część lejka działała przyzwoicie. A purchase tylko 37%. Strata była skoncentrowana między rozpoczęciem checkoutu a odpaleniem purchase, nie rozlana po całym lejku. To natychmiast wykluczało consent (ten gubiłby równomiernie) i kierowało uwagę na sam koniec ścieżki.
Kluczowy sygnał przyszedł z jednego współczynnika: add_payment_info do purchase wynosiło 97%. Czyli dla każdego, kto w ogóle dotarł do momentu purchase, tag odpalał się bezbłędnie. Tag nie był zepsuty. Problem nie polegał na tym, że purchase źle działa - tylko na tym, że większość użytkowników nigdy do niego nie docierała, mimo że realnie kończyła zakup i płaciła.
To zwęziło śledztwo do jednego pytania: co się dzieje między checkoutem a purchase, że dwie trzecie kupujących wypada z pomiaru, choć zamówienie ląduje w sklepie?
Root cause: redirect strony przejściowej ucina event
Odpowiedź siedziała w mechanice płatności online na Shoperze. I to jest najważniejsze uogólnienie: to nie jest problem jednej metody płatności. Dotyczy każdej płatności, która przekierowuje do zewnętrznej bramki - BLIK, Przelewy24, PayU, płatność kartą przez operatora. Schemat jest zawsze ten sam. Sklep nie przerzuca użytkownika prosto na thank-you page. Pomiędzy jest strona przejściowa (interstitial): ekran, na którym pushowany jest obiekt ecommerce i zdarzenie purchase do dataLayer, a następnie strona automatycznie przekierowuje do bramki płatności po kilku sekundach.
I tu jest cały dramat. purchase odpala beacon do serwera, ale redirect ucina request w locie, zanim dane zdążą dolecieć. Przeglądarka porzuca trwające żądania, gdy następuje nawigacja top-level. Strona przejściowa znika, zanim beacon się dopnie. A strona powrotna po płatności (thank-you) nie zawiera już zdarzenia purchase - bo logika sklepu pushuje je tylko raz, na interstitialu.
To wyjaśnia, dlaczego łapała się akurat ta część zakupów. Zamówienia za pobraniem nie mają redirectu do bramki - użytkownik zostaje na stronie, beacon spokojnie dolatuje, purchase się liczy. Każda płatność online, która przekierowuje do bramki, ten redirect ma i przez niego ginie. Proporcja płatności bez redirectu do tych z redirectem odbijała się jeden do jednego w pokryciu danych. To nie był przypadek ani sampling - to była struktura metod płatności widoczna wprost w liczbach.
Najważniejszy wniosek dla każdego, kto myśli, że server-side go ratuje: nie ratował. Server-side GTM (Stape) dostaje zdarzenie purchase z triggera, który i tak pochodzi z przeglądarki. Skoro browser nie zdążył wysłać beacona przed redirectem, serwer nie ma czego odebrać. Server-side dziedziczy ten sam wyścig - przesuwa go o jeden hop dalej, ale go nie wygrywa. Architektura serwerowa naprawia trwałość cookies i jakość danych, ale nie naprawia zdarzenia, które nigdy nie wyszło z przeglądarki.
Rozwiązanie: capture & replay
Skoro problem to wyścig między beaconem a redirectem, rozwiązanie nie polega na “naprawie tagu purchase” - tag jest sprawny. Polega na przeniesieniu momentu, w którym purchase realnie się wysyła, ze strony, która zaraz zniknie, na stronę, na której użytkownik faktycznie zostaje. Wzorzec nazywam capture & replay: złap dane tam, gdzie się pojawiają, odtwórz zdarzenie tam, gdzie jest bezpiecznie.
Krok 1: capture na stronie przejściowej
Na stronie przejściowej (tej z redirectem) wpinam tag Custom HTML, który nie wysyła purchase. Zamiast tego łapie obiekt ecommerce z dataLayer i zapisuje go do first-party cookie na domenie nadrzędnej (.domena.pl).
Klucz tkwi w tym, że zapis cookie jest synchroniczny. Nie czeka na sieć, nie czeka na odpowiedź serwera. Wykonuje się natychmiast w wątku JavaScript - i dlatego wygrywa wyścig z redirectem, którego beacon przegrywał. Zanim przeglądarka zacznie nawigację do bramki, cookie jest już zapisane.
Drobna, ale krytyczna pułapka przy łapaniu danych: obiekt ecommerce bywa pushowany OSOBNO od samego zdarzenia. Nie zakładaj, że event: 'purchase' i ecommerce siedzą na tym samym obiekcie w dataLayer. Trzeba przejść dataLayer i wziąć najświeższy obiekt zawierający ecommerce, niezależnie od tego, gdzie leci event. Założenie “wszystko jest na jednym pushu” to najczęstszy powód, że capture łapie pustkę.
Krok 2: replay na stronie powrotnej
Drugi tag siedzi na stabilnej stronie powrotnej po płatności - thank-you page, na której użytkownik realnie ląduje po powrocie z bramki i już nigdzie go nie przerzuca. Ten tag czyta cookie i odtwarza zdarzenie purchase: pushuje złapany obiekt ecommerce z powrotem do dataLayer.
Dopiero tutaj, na stronie bez redirectu, odpalają się tagi GA4, Google Ads i Meta - i tym razem beacon ma czas dolecieć, bo nic nie ucina nawigacji. Konwersja, która wcześniej ginęła na interstitialu, teraz materializuje się na stronie, gdzie użytkownik spokojnie czeka.
Najprostszy dowód, że to działa, widać w konsoli na stronie powrotnej. Wcześniej dataLayer.filter(x => x.event === 'purchase') zwracało tu pustą tablicę [] - zdarzenia po prostu nie było. Po wdrożeniu replaya to samo zapytanie zwraca odtworzony obiekt purchase z kompletnym ecommerce i flagą _replayed: true, którą doklejam przy odtwarzaniu, żeby odróżnić zdarzenie capture & replay od oryginalnego pushu.

Krok 3: atrybucja i ochrona przed dublami
Capture & replay rozwiązałby pomiar, ale po drodze nie wolno zgubić dwóch rzeczy: atrybucji Google Ads i deduplikacji.
Atrybucja: gclid musi przeżyć całą podróż przez bramkę z powrotem na thank-you page. Przeżywa, bo jest trzymany w first-party cookie (Conversion Linker po stronie tagów plus trwałość cookies pod ITP - mechanizm opisany w trwałości identyfikatorów a match rate). Dzięki temu purchase odtworzony na stronie powrotnej nadal niesie kliknięcie reklamy, które go zapoczątkowało.
Deduplikacja: ponieważ purchase teraz odpala na stronie powrotnej, a w rzadkich przypadkach może odpalić też w starym miejscu, deduplikacja po transaction_id chroni przed podwójnym liczeniem. To ta sama warstwa, która wcześniej była podejrzana o ucinanie danych - tu pełni rolę bezpiecznika, gdyby zdarzenie odpaliło dwa razy.
Lekcje inżynierskie z tej naprawy
Cztery rzeczy z tej roboty warto zapamiętać niezależnie od tego, czy Twój sklep ma akurat ten problem.
first-party cookie przeżywa redirect, localStorage nie. Naturalny odruch to zapisać dane w localStorage - jest wygodny i pojemny. Ale localStorage jest per dokładny host: www.domena.pl i domena.pl to dwa osobne magazyny, a po przekierowaniu na inny origin dane mogą być niedostępne. First-party cookie ustawione na .domena.pl przeżywa subdomeny, a przy nawigacji top-level (SameSite=Lax) leci razem z żądaniem. Do przeniesienia stanu przez redirect cookie jest właściwym narzędziem, nie localStorage.
Nie zakładaj, że event i ecommerce są na tym samym obiekcie dataLayer. Opisane wyżej - implementacje sklepów często pushują dane ecommerce osobnym wywołaniem niż sam event: 'purchase'. Bierz najświeższy obiekt z ecommerce, nie ten, na którym akurat siedzi event.
Replay musi odtworzyć źródło event_id, nie tylko ecommerce. To najłatwiejsza do przeoczenia pułapka całego mechanizmu. Deduplikacja zadziała tylko wtedy, gdy odtworzone zdarzenie ma ten sam event_id co oryginał. A event_id (w GA4 i w Meta) bywa budowany jako purchase_ plus transaction_id czytany z eventModel.transaction_id - pola, które tworzy dopiero push w stylu gtag, a nie zwykły dataLayer.push. Jeśli replay poda samo ecommerce, event_id wychodzi pusty (purchase_), deduplikacja przestaje łapać i wracasz do ryzyka podwójnego liczenia, które ten mechanizm miał wykluczyć. W replay’u trzeba ustawić eventModel.transaction_id wprost. Sprawdzisz to w jednej linii w konsoli na stronie powrotnej: evnid w beaconie purchase ma kończyć się numerem zamówienia, nie samym purchase_.
Triangulacja przed porównaniem narzędzi. Cała ta naprawa wyszła wyłącznie dlatego, że pierwszym ruchem nie było “Google Ads vs GA4”, tylko zestawienie GA4 z realnymi zamówieniami. Gdyby porównywać dwa panele reklamowe między sobą, problem wyglądałby na atrybucję i nigdy nie dotarłoby się do redirectu bramki. Niezależne źródło prawdy to nie formalność - to jedyny punkt, od którego diagnoza w ogóle ma sens.
Efekt
Po wdrożeniu capture & replay sklep odzyskał gros gubionych konwersji z płatności online - czyli dokładnie tę część, która wcześniej ginęła na stronie przejściowej z redirectem do bramki płatności. GA4 i Google Ads zaczęły się zgadzać z realną sprzedażą z panelu sklepu, a triangulacja, która rozpoczęła całe śledztwo, w końcu się domknęła.
Co istotne, samo zgłoszenie - “Google Ads przekłamuje względem GA4” - było objawem, nie chorobą. Choroba siedziała dwa poziomy głębiej, na stronie, której nikt nie podejrzewał, bo formalnie wszystko było skonfigurowane poprawnie. Tag purchase działał. Server-side działał. Consent działał. A połowa zakupów i tak nie istniała w danych, bo redirect ucinał beacon, zanim ten zdążył wystartować.
Podsumowanie
GA4 gubiące część zakupów to często nie problem zgody, nie duplikacja i nie zepsuty tag purchase. To wyścig: zdarzenie purchase odpala na stronie, która zaraz przekierowuje do bramki płatności, a redirect ucina beacon, zanim dane dolecą. Płatności bez redirectu (za pobraniem) łapią się normalnie, każda online z przekierowaniem do bramki ginie - i to dotyczy dowolnej metody (BLIK, Przelewy24, PayU, karta), nie jednej konkretnej.
Naprawą jest capture & replay: synchroniczny zapis obiektu ecommerce do first-party cookie na stronie przejściowej (wygrywa wyścig z redirectem) i odtworzenie zdarzenia purchase na stabilnej stronie powrotnej (gdzie beacon ma czas dolecieć). Atrybucję chroni gclid w cookie, przed dublami broni deduplikacja po transaction_id. Server-side sam tego nie ratuje - trigger i tak pochodzi z przeglądarki i dziedziczy ten sam wyścig.
Jeśli GA4 pokazuje mniej zakupów niż panel sklepu, nie zaczynaj od atrybucji. Rozłóż lejek na zdarzenia i sprawdź, czy purchase nie odpala się na stronie, która znika, zanim dane zdążą wyjść.