
Problem: Sklep e-commerce na WooCommerce gubił około 40% transakcji w Google Analytics 4. Bramka płatności Tpay robiła długi redirect, użytkownik płacił i zamykał kartę, nigdy nie wracając na stronę podziękowania. Bez powrotu na Thank You Page nie odpalał się event purchase. Pokrycie transakcji: około 60%.
Rozwiązanie: Server-side tagging oparty o Stape.io i Cloudflare, plus awaryjny webhook z WooCommerce do serwerowego GTM. Serwer wysyła transakcję niezależnie od tego, czy przeglądarka wróciła na stronę. Inteligentny system opóźnionego fallbacku eliminuje duplikaty.
Wynik: Pokrycie transakcji w GA4 wzrosło z około 60% do praktycznie 100%. Zero duplikatów, pełna atrybucja źródła ruchu, zgodność z Consent Mode v2.
Stack: Google Tag Manager (Web + Server), GA4, Stape.io, Stape Store, Cloudflare Workers, WooCommerce, Consent Mode v2
01 / 03
Zacznę od mitu, który słyszy każdy właściciel sklepu."GA4 i tak nigdy nie policzy wszystkich zakupów. Nawet najlepsze wdrożenia mają luki." To prawda. Standardowe, client-side śledzenie zakupów opiera się na jednym kruchym założeniu: że przeglądarka użytkownika wróci na stronę podziękowania (Thank You Page) i zdąży odpalić tam event purchase. Adblockery, ITP w Safari, gubione requesty, zamknięte karty. Każdy z tych czynników odgryza kawałek danych. Dlatego branża przyjęła, że 80-85% pokrycia transakcji to bardzo dobry wynik.
W tym wdrożeniu zaczynałem znacznie poniżej tego pułapu, a skończyłem powyżej niego. Punkt startowy: około 60% pokrycia. Punkt docelowy: praktycznie 100%. To nie był sklep z drobnymi zamówieniami. Produkty z wyższej półki cenowej, realne pieniądze na każdej transakcji. A mimo to cztery zakupy na dziesięć były dla Google Analytics niewidzialne.
Winowajca był jeden i konkretny: bramka płatności Tpay. Mechanizm wyglądał tak. Klient klikał "Kupuję i płacę", trafiał na Tpay, wybierał bank albo BLIK, płacił. Po płatności Tpay robił długi redirect z powrotem do sklepu. I tu kończyła się historia.
Realny użytkownik, szczególnie na telefonie, nie czeka na powolne przekierowanie. Płaci i zamyka kartę. Z jego perspektywy transakcja jest skończona - dostał potwierdzenie z banku. Z perspektywy analityki nie wydarzyło się nic, bo:
przeglądarka nigdy nie załadowała strony podziękowania,
event purchase po stronie klienta nigdy nie wystrzelił,
GA4 nie dostawał żadnego sygnału o zakupie.
Dawało to lukę rzędu 40% transakcji. Skąd wiem, że to akurat 40%? Brakujących zakupów z definicji nie widać w samym GA4, więc nie da się ich tam zliczyć. Punktem odniesienia jest panel WooCommerce, który rejestruje każde opłacone zamówienie niezależnie od tego, co zrobiła przeglądarka. Zestawiłem liczbę opłaconych zamówień w sklepie z liczbą zdarzeń purchase w GA4 za ten sam okres. Stosunek tych dwóch liczb to realne pokrycie transakcji. Wyszło około 60%.
Brakujące 40% to nie jest "kosmetyczny błąd w raporcie". To fundament, na którym podejmuje się decyzje budżetowe.
Algorytmy Google Ads i Meta uczyły się na okrojonych danych. Sygnał konwersji wysyłany był do systemu reklamowego tylko dla części klientów, więc optymalizacja celowała w niepełny obraz.
Przychód w GA4 był zaniżony. Kampania, która realnie zarabiała, w raporcie wyglądała na ledwie rentowną. Ryzyko wyłączenia dobrej kampanii rosło z każdym dniem.
Najgorsze było to, co działo się w głowie właściciela. Podejmował decyzje budżetowe na podstawie raportów, którym po cichu przestał ufać - liczby z GA4 nie zgadzały się z tym, co widział na koncie bankowym sklepu. A analityka, której się nie ufa, jest gorsza niż jej brak, bo daje fałszywe poczucie kontroli.
Mówiąc wprost: klient płacił za reklamę, która działała, ale w danych tego nie widział. A to najszybsza droga do ucięcia budżetu w złym miejscu.
02 / 03
Kiedy mówi się o server-side tagging, większość artykułów zatrzymuje się na dwóch korzyściach: dłuższych ciasteczkach i odporności na adblockery. To prawda, ale to dopiero wierzchołek góry lodowej. Obie te rzeczy wciąż zakładają, że użytkownik jest na stronie, a jego przeglądarka cokolwiek wysyła.
Prawdziwa moc tego podejścia ujawnia się dokładnie wtedy, gdy przeglądarki nie ma już w grze. Skoro dane analityczne przetwarza własny serwer, a nie przeglądarka, to konwersję można do niego wysłać z dowolnego źródła. Nie tylko z przeglądarki, ale również z backendu sklepu, w momencie gdy WooCommerce potwierdza opłacone zamówienie. Zupełnie niezależnie od tego, czy klient wrócił z Tpay, czy zamknął kartę pięć sekund wcześniej.
To jest ten moment, w którym server-side tagging przestaje być "lepszym trackingiem", a staje się siatką bezpieczeństwa dla transakcji. I to jest też moment, w którym otwiera nowe możliwości: kompletny strumień konwersji do zasilenia algorytmów reklamowych, czego client-side nigdy nie dowiezie.
Fundament był już postawiony wcześniej: kontener server-side GTM hostowany na Stape.io, podpięty przez Cloudflare Workers w trybie same-origin, czyli ruch leci na własną domenę sklepu, nie do zewnętrznej subdomeny. To daje maksymalną odporność na adblockery i ITP.
Na tym fundamencie zbudowałem mechanizm odzyskiwania transakcji. Logika jest prosta i to w niej tkwi cały spokój całej konstrukcji: przeglądarka ma pierwszeństwo, a serwer wchodzi do gry tylko wtedy, gdy przeglądarka zawiedzie. Dzięki temu nigdy nie liczy tej samej transakcji dwa razy.
Sercem rozwiązania jest Stape Store - lekka baza danych typu klucz-wartość, działająca wewnątrz serwerowego kontenera GTM. To ona pełni rolę "pamięci podręcznej" pomiędzy przeglądarką a webhookiem, i to ona spina całą dwuźródłową logikę w spójną całość. Cały przepływ rozgrywa się w trzech aktach.
Akt 1: Przeglądarka zapisuje odcisk sesji, jeszcze w trakcie checkoutu. Zanim klient w ogóle dojdzie do płatności, na wczesnych krokach lejka (begin_checkout, add_payment_info) przeglądarka zapisuje w Stape Store komplet danych potrzebnych później do złożenia poprawnej transakcji: client_id, session_id, status zgody z Consent Mode, prawdziwy adres IP oraz dane urządzenia.
Akt 2: Jeśli przeglądarka wraca, zostawia flagę. Gdy klient mimo wszystko trafi na Thank You Page, Web GTM wysyła normalny purchase, a dodatkowo zapisuje w bazie flagę { "status": "processed" } powiązaną z numerem zamówienia. To sygnał dla serwera: "tym zamówieniem już się zająłem".
Akt 3: Webhook sprawdza, czy jest w ogóle potrzebny. Webhook z WooCommerce dociera na serwer celowo opóźniony o 3 minuty. To opóźnienie daje przeglądarce czas, żeby zdążyła pierwsza. Serwer odpytuje bazę i podejmuje decyzję:
Znalazł flagę? Przeglądarka dała radę. Serwer kasuje webhook i nie robi nic. Zero duplikatów.
Brak flagi? Przeglądarka poległa. Serwer wyciąga z bazy zapisany wcześniej client_id oraz inne informacje potrzebne do złączenia sesji, doszywa je do danych z webhooka i wysyła kompletną transakcję do GA4.
Efekt jest taki, że GA4 dostaje zakup nawet od klienta, który zamknął kartę. I to nie jako anonimowy "Direct", lecz z prawidłowym client_id, przypisany do realnego źródła ruchu, gotowy do zasilenia raportów ROAS i algorytmów licytacji.
Na diagramie wszystko wygląda prosto. W praktyce, zanim system działał bezbłędnie, musiałem rozbroić pięć pułapek. Opisuję je, bo każda z nich to klasyczny błąd server-side tagging, na którym wykłada się większość wdrożeń.
Wyzwanie 1: odwrócony wyścig danych (race condition). W pierwszej wersji webhook potrafił dotrzeć na serwer zanim przeglądarka zdążyła zapisać dane sesji. Przy błyskawicznych płatnościach BLIK serwer pytał bazę o dane, których jeszcze tam nie było, i dostawał błąd 404, a transakcja zostawała osierocona. Rozwiązanie było dwutorowe: przesunąłem zapis sesji na wczesne kroki lejka oraz świadomie opóźniłem webhook o 3 minuty. W praktyce ten jeden błąd uderzał najpierw w najszybciej płacących klientów, bo system ratunkowy uruchamiał się o ułamek sekundy za wcześnie - i to właśnie najsprawniejsi kupujący znikali z raportów jako pierwsi.
Wyzwanie 2: e-mail to fatalny klucz bazy. Pierwotnie kluczem dokumentu w bazie był adres e-mail klienta i była to bomba z opóźnionym zapłonem. Klient na telefonie wpisywał [email protected] z autokorekty albo zostawiał spację na końcu. Baza rozróżnia wielkość liter, więc webhook szukał innego rekordu niż ten, który zapisała przeglądarka, i znów dostawał 404. Najpierw poszedłem najtańszą drogą: wymusiłem standaryzację e-maila po obu stronach - sprowadzenie do małych liter i obcięcie białych znaków, żeby przeglądarka i webhook zapisywały oraz odpytywały bazę dokładnie tym samym kluczem. To załatało najczęstsze przypadki, ale dane wciąż się rozjeżdżały, tylko rzadziej. Standaryzacja leczyła objawy, nie przyczynę - klucz dalej zależał od tego, co człowiek wklepie z telefonu. Dlatego przeszedłem na cart_hash - 32-znakowy, unikalny identyfikator koszyka, który WooCommerce generuje sam, całkowicie niezależny od ręcznego wpisu. Dopiero to dało klucz w 100% deterministyczny. To był jeden z najważniejszych zwrotów akcji w projekcie.
Wyzwanie 3: [object Object] w payloadzie. Tag GA4 próbował wysłać cały wielopoziomowy obiekt ecommerce jako jeden ciąg tekstowy, co kończyło się błędem serializacji [object Object]. Wykluczyłem surowy obiekt z wysyłki i zmapowałem pola transakcji ręcznie, dzięki czemu hit do GA4 zrobił się czysty. Biznesowo był to poważny problem: w tym stanie systemy reklamowe odrzucały dane o wartości i zawartości koszyka. Wiedziały, że ktoś kupił, ale nie wiedziały co i za ile, więc dostawały sygnał kompletnie bezużyteczny do optymalizacji.
Wyzwanie 4: zgodność z prawem (Consent Mode v2). Transakcja wysyłana z serwera musi nieść informację o zgodzie użytkownika. Dlatego w Akcie 1 zapisuję w bazie również stan zgody klienta, a przy wysyłce serwerowej odtwarzam parametr gcs (np. G111). Serwer mówi Google wprost: "ten użytkownik wyraził zgodę, oto dowód".
Wyzwanie 5: transakcja "od serwera", nie od klienta. Webhook leci z backendu WooCommerce, więc bez korekty GA4 przypisałby każdy zakup do serwera sklepu, z User-Agentem WordPress i adresem IP serwerowni. Cała geografia oraz podział mobile/desktop poszłyby do kosza. Dlatego serwer nadpisuje IP i dane urządzenia wartościami klienta, zapisanymi wcześniej w bazie. Bez tej korekty każdy odzyskany zakup wyglądałby tak, jakby dokonał go serwer sklepu, a nie realny klient z Warszawy na iPhonie - a raporty geograficzne i podział na urządzenia, na których właściciel opiera decyzje reklamowe, stałyby się czystą fikcją.
03 / 03
Najpierw uczciwe zastrzeżenie. Trzeba rozdzielić dwie rzeczy, które w typowym wdrożeniu lądują w jednym worku: samo zliczenie transakcji oraz jakość jej atrybucji.
Zliczenie jest praktycznie pewne. Webhook leci z backendu WooCommerce do serwera w komunikacji serwer-serwer, więc nie zależy od przeglądarki klienta ani od tego, co ma w niej zainstalowane. Dociera zawsze, a wraz z nim cały payload zakupu: wartość, zawartość koszyka, numer zamówienia. Z perspektywy pytania "ile naprawdę sprzedaliśmy" luka praktycznie znika, bo do policzenia transakcji client_id nie jest potrzebny.
Czego nie obiecuję jako matematycznego 100,00%, to idealna atrybucja każdej pojedynczej transakcji. Jeśli przeglądarka z jakiegoś powodu nie zdążyła zapisać odcisku sesji w Stape Store, webhook nie znajdzie client_id, który mógłby doszyć do zakupu. Taka transakcja nadal zostaje policzona, ale wpada bez przypisania do źródła, najczęściej jako unassigned. Przychód się zgadza, tylko konkretna kampania nie dostaje za niego zasługi. To wąski margines i dotyczy jakości danych, nie utraconej sprzedaży.
Z tym zastrzeżeniem liczby mówią same za siebie:
Pokrycie transakcji: z około 60% do niemal 100%. Ten sam pomiar co na starcie (opłacone zamówienia w WooCommerce kontra zdarzenia purchase w GA4) pokazuje, że luka 40% praktycznie zniknęła. Transakcje, które wcześniej ginęły na długim redirekcie Tpay, teraz wpadają przez webhook server-side.
Zero duplikatów. System opóźnionego fallbacku gwarantuje, że transakcja jest liczona dokładnie raz: albo z przeglądarki, albo z serwera, nigdy z obu naraz. Serwer sam blokuje się, gdy widzi flagę przeglądarki.
Odzyskana atrybucja źródła. W zdecydowanej większości przypadków transakcje nie wpadają już do worka Unassigned". Niosą prawidłowy client_id i session_id, więc GA4 przypisuje przychód do realnej kampanii, a nie do anonimowego ruchu.
Zgodność z Consent Mode v2. Każdy serwerowy hit niesie odtworzony status zgody (gcs), więc Google przyjmuje go legalnie. Zero ryzyka po stronie RODO.
Algorytmy reklamowe dostają komplet sygnałów. Pełny obraz konwersji to lepsza optymalizacja Google Ads i Meta, bo systemy uczą się na wszystkich klientach, a nie na sześciu z dziesięciu. To jest dokładnie ta nowa zdolność wzrostu, którą wdrożenie odblokowuje: budżet zaczyna pracować na pełnych danych.
Jeśli próbujesz zbudować podobny mechanizm odzyskiwania transakcji, oto miny, na które z dużym prawdopodobieństwem wejdziesz.
1. Opóźnij webhook. Daj przeglądarce pierwszeństwo. Jeśli serwer i przeglądarka wyślą purchase jednocześnie, masz albo duplikat, albo wyścig danych. Opóźnienie webhooka (u mnie 3 minuty przez Action Scheduler) sprawia, że serwer staje się fallbackiem, a nie konkurentem przeglądarki.
2. Nie używaj e-maila jako klucza bazy. Wielkość liter, spacje, autokorekta na telefonie - e-mail to mało stabilny identyfikator. Użyj czegoś deterministycznego, co generuje sam system: cart_hash z WooCommerce albo numer zamówienia.
3. Wyklucz surowy obiekt ecommerce z payloadu. Próba wysłania wielopoziomowego obiektu jako tekstu daje [object Object] i psuje cały hit. Mapuj pola transakcji ręcznie: transaction_id, value, items.
4. Odtwórz zgodę i nadpisz dane urządzenia. Hit bez nadpisanego IP i User-Agenta zostanie odrzucony lub przypisany do serwerowni, nie do klienta. Zapisz jedno i drugie w bazie na etapie checkoutu i odtwórz przy wysyłce.
5. Debuguj na poziomie bazy, nie interfejsu. Podgląd GTM potrafi wprowadzić w błąd, na przykład wizualnie obciąć client_id. Weryfikuj dane tam, gdzie naprawdę żyją: w bazie Stape Store i w surowych logach żądań do GA4. Inaczej tracisz godziny na ściganie problemu, którego nie ma.
Usługa: Server-Side Tagging →
Umów bezpłatną konsultację i sprawdź, jak mogę pomóc Twojemu biznesowi.