Measurement Protocol w GA4 przydaje się tam, gdzie przeglądarka nie ma nic do powiedzenia: zamówienia z ERP, zamówienia telefoniczne, zwroty księgowane po kilku dniach. Wtedy purchase i refund wysyłasz z serwera — z tym samym transaction_id, żeby zwrot trafił w pierwotną transakcję. Poniżej znajdziesz działający request, rozpisany payload i listę błędów, które najczęściej psują raporty e-commerce. Jedna rzecz na start: MP nie zastępuje tagu w przeglądarce, tylko go uzupełnia.
Measurement Protocol wygrywa w czterech konkretnych sytuacjach i tylko w nich warto go wdrażać:
Różnica nie polega na tym, że MP jest „lepszy”. Client-side (gtag, GTM, dataLayer) odpala się tylko wtedy, gdy przeglądarka wykona JavaScript. Blokada reklam, brak zgody w CMP, przeładowanie strony po kliknięciu „Kupuję” — każde z nich oznacza utracony purchase. Przy typowym ruchu konsumenckim to nie 2%, a często 5–15% transakcji. MP wysyła żądanie z serwera Twojej aplikacji, więc te blokady go nie dotyczą.
Ale MP ma twarde ograniczenie: zdarzenie nie pojawi się w raportach zaangażowania (Zaangażowanie, Ścieżki, Eksplorator użytkowników), jeśli nie dołączysz engagement_time_msec i session_id. Bez nich GA4 przyjmie przychód, ale nie przypisze zdarzenia do sesji. Druga rzecz: MP nie odtworzy źródła ruchu. Zamówienie telefoniczne bez session_id wyląduje jako direct/none — kanał, który je wygenerował, zniknie z raportu.
Zasada do trzymania: jedno źródło prawdy dla transakcji. Albo purchase wysyła przeglądarka, a MP tylko uzupełnia braki (telefon, ERP, zwroty), albo wysyła wyłącznie serwer — nigdy oba dla tego samego transaction_id.
| Sytuacja | gtag / GTM / dataLayer | Measurement Protocol |
|---|---|---|
| Zamówienie w przeglądarce, JS działa | Wysyła, pełne dane o sesji | Niepotrzebny |
| Blokada reklam lub brak zgody w CMP | Zdarzenie ginie | Wysyła z serwera |
| Zamówienie telefoniczne lub z ERP | Brak zdarzenia | Wysyła, wymaga client_id i session_id |
| Zwrot księgowany po dniach | Brak sesji, brak zdarzenia | Wysyła z transaction_id |
| Przypisanie do sesji i kanału | Automatyczne | Tylko przy session_id i engagement_time_msec |
Adres: https://www.google-analytics.com/mp/collect?measurement_id=G-XXXXXXX&api_secret=XXXXXXXX. Metoda POST, nagłówek Content-Type: application/json. Oba parametry idą w query stringu, nie w body.
Skąd je wziąć:
measurement_id — Administracja → Strumienie danych → wybierz strumień WWW. Format: G- z dziesięcioma znakami.api_secret — ten sam ekran → Measurement Protocol API secrets → Utwórz. Utwórz osobny sekret dla każdej integracji (ERP, zwroty, subskrypcje). Gdy będziesz rotować jeden z nich, nie unieruchomisz całego e-commerce. Sekret trzymaj w zmiennych środowiskowych, nigdy w kodzie frontendu.Limity, które wyznaczają architekturę wysyłki: maksymalnie 25 zdarzeń w jednym żądaniu i do 1 MB payloadu. 500 zaległych zamówień z ERP to minimum 20 żądań — zaplanuj pętlę z paczkowaniem po 25 i krótkim opóźnieniem między żądaniami.
Minimalne body, które GA4 przyjmie:
{
"client_id": "1234567890.1698765432",
"events": [
{
"name": "purchase",
"params": {
"transaction_id": "ZAM-2024-1938",
"currency": "PLN",
"value": 249.00
}
}
]
}Skąd client_id: z ciasteczka _ga, czyli część po GA1.1. (np. 1234567890.1698765432). Jeśli zamówienie przyszło z telefonu i nie masz tego identyfikatora, wygeneruj własny, zapisz go przy kliencie w CRM i używaj konsekwentnie w kolejnych zdarzeniach. Bez client_id GA4 potrafi przypisać zdarzenie do losowego identyfikatora, a walidacja zwrócić błąd 400 — tracisz możliwość powiązania zamówień jednego klienta.
Zanim puścisz cokolwiek na produkcję, wyślij ten sam request na https://www.google-analytics.com/debug/mp/collect. Zwraca JSON z sekcją validationMessages, w której zobaczysz brakujące lub źle nazwane parametry. Produkcyjny endpoint odpowiada pustym 204 i nie powie Ci, co zrobiłeś źle.
Kompletny zestaw parametrów dla zakupu wygląda tak: transaction_id (unikalny, do 500 znaków), currency w standardzie ISO 4217, value, tax, shipping, coupon oraz tablica items[]. Do tego engagement_time_msec i session_id — bez nich nie zobaczysz zdarzenia w raportach zaangażowania.
Parametr nazywa się session_id, a wartość bierzesz z ga_session_id, czyli drugiej liczby z ciasteczka _ga_<container> albo z parametru session_id przekazanego przez gtag do serwera przy składaniu zamówienia. Zapisz ją razem z zamówieniem w bazie — inaczej nie odtworzysz jej po fakcie.
Dwie pułapki, które psują raporty najczęściej:
value bez items — dostajesz przychód bez danych produktowych. Raporty „Sprzedane produkty” i „Przychód z pozycji” zostają puste, nie odpowiesz na pytanie, co się sprzedaje.items bez wartości liczbowych — price musi być liczbą (234.00), nie stringiem („234,00 zł”). GA4 policzy przychód pozycji jako 0 i suma pozycji rozjedzie się z value. To samo dotyczy quantity — liczba całkowita, nie „2 szt.”.Przyjmij jedną zasadę i się jej trzymaj: value = kwota faktycznie zapłacona przez klienta (z dostawą i podatkiem, po rabacie). Wtedy tax i shipping są tylko informacją dodatkową w raporcie.
Pełny JSON z jedną pozycją, gotowy do podmiany wartości:
{
"client_id": "1234567890.1698765432",
"events": [
{
"name": "purchase",
"params": {
"transaction_id": "ZAM-2024-1938",
"currency": "PLN",
"value": 249.00,
"tax": 46.54,
"shipping": 15.00,
"coupon": "WIOSNA10",
"engagement_time_msec": 100,
"session_id": "1698765400",
"items": [
{
"item_id": "SKU-1042",
"item_name": "Fotel biurowy Ergofix 200",
"price": 234.00,
"quantity": 1,
"item_brand": "Ergofix",
"item_category": "Meble biurowe"
}
]
}
}
]
}Pola item_id, item_name, price, quantity, item_brand i item_category mapuj wprost z pozycji zamówienia w sklepie — w WooCommerce to line items, których strukturę opisuje dokumentacja WooCommerce. Nie wymyślaj własnych nazw parametrów: GA4 nie ma dla nich slotu i po cichu je zignoruje.
| Parametr | Typ | Przykład | Uwagi |
|---|---|---|---|
| transaction_id | string | ZAM-2024-1938 | Unikalny, do 500 znaków; ten sam przy zwrocie |
| currency | string | PLN | Kod ISO 4217, nie symbol |
| value | number | 249.00 | Kwota zapłacona przez klienta |
| tax | number | 46.54 | Podatek wliczony w value |
| shipping | number | 15.00 | Koszt dostawy wliczony w value |
| coupon | string | WIOSNA10 | Kod rabatu, jeśli został użyty |
| items[] | array | 1 pozycja | Bez tego raporty produktowe są puste |
| price | number | 234.00 | Liczba, nie string |
| quantity | integer | 1 | Liczba całkowita |
| engagement_time_msec | integer | 100 | Warunek raportów zaangażowania |
| session_id | string | 1698765400 | Wartość ga_session_id z przeglądarki |
Zdarzenie refund pomniejszy przychód tylko wtedy, gdy zawiera dokładnie ten sam transaction_id, który wcześniej wysłałeś w purchase. GA4 szuka po nim transakcji. Wystarczy, że ERP dopisze przedrostek (WEB-ORD-10241 zamiast ORD-10241) i zwrot nie połączy się z zakupem — przychód w raporcie rośnie, a zwroty wiszą osobno jako osobne zdarzenia.
Zwrot całego zamówienia to jeden refund z transaction_id i pełną kwotą zwrotu w value. Zwrot pojedynczej pozycji to refund, w którym przekazujesz wyłącznie zwracane items, z ujemną quantity i wartością zwrotu. Reszta zamówienia zostaje nienaruszona — dlatego to dwa różne payloady i dwa różne skutki w raportach.
Przykład zwrotu częściowego (jeden z trzech produktów):
{"client_id":"1234567890.1716200000","events":[{"name":"refund","params":{"transaction_id":"ORD-10241","currency":"PLN","value":-89.9,"items":[{"item_id":"SKU-4410","quantity":-1,"price":89.9}]}}]}
Kolejność zdarzeń ma znaczenie: refund musi trafić po purchase. Zwrot wysłany przed zakupem nie ma czego pomniejszyć i przepada bez śladu w raportach.
Najczęstsza pułapka to czas. Zwroty księgowane w ERP po kilku dniach muszą przejść przez kolejkę, która wyśle zdarzenie z timestamp_micros odpowiadającym momentowi księgowania — nie dacie zamówienia. GA4 odrzuca zdarzenia starsze niż 72 godziny, a znacznik z przyszłości również jest odrzucany. Kolejkę projektuj jako at-least-once: klucz refund_{transaction_id}_{refund_id}, log statusów i kontrolowane ponowienia, żeby jeden nieudany request nie zniknął w ciszy. Jeśli sklep działa na PrestaShop, całą operację podpina się pod hook zamówienia — mechanikę hooków opisuje dokumentacja dla deweloperów PrestaShop.
| Wariant | Co wysyłasz | Skutek w raportach |
|---|---|---|
| Zwrot całego zamówienia | refund z transaction_id i pełną kwotą zwrotu w value | Transakcja ma odjęty cały przychód |
| Zwrot jednej pozycji | refund z transaction_id, value = kwota zwrotu, items tylko ze zwracanym SKU i quantity -1 | Pomniejszony przychód o wartość zwróconej pozycji, reszta zamówienia bez zmian |
GA4 ma wbudowaną deduplikację purchase po transaction_id, ale ona nie zastąpi spójnej architektury wysyłki. Nie naprawi sytuacji, w której ten sam zakup leci dwa razy z dwóch różnych źródeł i z dwoma różnymi client_id — w raportach zobaczysz dwa zakupy, podwójny przychód i podwójną konwersję.
Typowy scenariusz: GTM wysyła purchase z przeglądarki na stronie „Dziękujemy”, a integracja z ERP wysyła to samo zdarzenie po zmianie statusu na „opłacone”. Jeśli ERP nie pobrało cookie _ga z zamówienia, wysyła inny client_id i nic ich nie połączy.
Rekomendacja: jedno źródło prawdy. purchase wysyła serwer, a tag w przeglądarce zostaje wyłączony — nie „ukryty”, tylko usunięty z kontenera GTM. Wtedy nie musisz zgadywać, które zdarzenie wygrało w raportach.
purchase_{transaction_id}, blokada powtórnej wysyłki na poziomie bazy, statusy pending/sent/failed i osobny log requestów.purchase przy każdej zmianie statusu, bo część sklepów ma ich pięć.Wykrycie dubli: w eksporcie GA4 do BigQuery porównaj COUNT(DISTINCT transaction_id) z liczbą wierszy purchase w tym samym okresie. Każda różnica to albo duplikat, albo zdarzenie bez transaction_id — jedno i drugie psuje raporty.
| Objaw | Przyczyna | Naprawa |
|---|---|---|
| Podwójny przychód w e-commerce | purchase z GTM i z serwera, dwa różne client_id | Wyłącz tag w przeglądarce, zostaw tylko serwer |
| Zakup przypisany do (direct)/(none) | brak client_id i session_id w zdarzeniu z serwera | Zapisz cookie _ga w zamówieniu i przekazuj je w payloadzie |
| Ten sam zakup policzony dwa razy po awarii | brak idempotencji w kolejce, ponowienie po timeoucie | Klucz purchase_{transaction_id} i statusy wysyłki w bazie |
Zdarzenie wysłane z serwera bez identyfikatorów wejdzie do raportów jako (direct)/(none) lub „Unassigned”. Liczba się zgadza, atrybucja kanałowa już nie — i cały sens serwerowej wysyłki znika.
client_id pochodzi z cookie _ga i ma format GA1.1.XXXXXXXXXX.YYYYYYYYYY. Identyfikatorem jest część po ostatniej kropce w wersji z prefiksem, czyli dwa ostatnie segmenty. Zapisz go w zamówieniu w momencie składania — polem ukrytym w formularzu albo metą zamówienia. Z ERP go nie odtworzysz. Jeśli cookie nie ma (brak zgody na analitykę, blokada, wygasło), nie zmyślaj wartości: albo wyślij zdarzenie bez client_id, albo wygeneruj własny identyfikator i zaakceptuj, że w GA4 powstanie nowy „użytkownik”.
user_id to identyfikator zalogowanego klienta z systemu sklepu, np. numer konta. Wysyłaj go w formie zhashowanej — SHA-256 z solą dodaną po stronie serwera. Nigdy nie wysyłaj e-maila ani loginu: to dane osobowe, a GA4 nie przyjmuje PII. Hash musi być stały dla danego klienta, inaczej rozjedzie ci się ścieżka cross-device.
session_id (ga_session_id) pobierzesz z cookie _ga_<ID_kontenera> lub ze zdarzenia session_start. Przekazany razem z client_id pozwala wpiąć zdarzenie w istniejącą sesję, zamiast tworzyć nową — sesja z kampanii nie rozbije się na dwie i kanał nie zostanie zgubiony.
timestamp_micros ustawiaj ostrożnie: zdarzenia wsteczne mają limit 72 godzin, a znacznik z przyszłości jest odrzucany. Zwrot księgowany po pięciu dniach wejdzie z datą wysyłki, nie z datą zamówienia — zaplanuj to w raportowaniu.
| Parametr | Skąd wziąć | Format / uwaga | Bez tego |
|---|---|---|---|
| client_id | cookie _ga z zamówienia | GA1.1.XXXXXXXXXX.YYYYYYYYYY | Zdarzenie bez atrybucji kanałowej |
| user_id | ID konta klienta | SHA-256 z solą, bez PII | Brak łączenia sesji cross-device |
| session_id | cookie _ga_<ID_kontenera> | liczba całkowita, ga_session_id | Powstaje nowa sesja, kanał się gubi |
| timestamp_micros | moment zdarzenia | do 72 h wstecz, przyszłość odrzucana | Zdarzenie odrzucone przez GA4 |
W PrestaShop zdarzenie zakupu podpinasz pod hook actionValidateOrder — odpala się w momencie zatwierdzenia zamówienia, a w parametrach dostajesz obiekt order, z którego bierzesz id, total_paid, id_currency i listę pozycji przez getProducts(). Osobnego hooka „refund” nie ma — obserwujesz zmianę statusu w actionOrderStatusPostUpdate (parametry newOrderStatus, id_order). Jeśli nowy status to ten skonfigurowany u Ciebie jako „Zwrócono”, wysyłasz refund. Warunek: kwota zwrotu to nie total_paid, tylko wartość faktycznie zaksięgowana — inaczej zwroty częściowe zawyżą korektę przychodu.
W WooCommerce masz dwie drogi. Push: webhooki z topicami order.completed i refund.created, podpisane nagłówkiem X-WC-Webhook-Signature (HMAC-SHA256 na surowym body) — podpis weryfikuj, bo endpoint przyjmujący dane z zewnątrz bez weryfikacji to zaproszenie do śmieci w raportach. Pull: REST API, np. GET /wp-json/wc/v3/orders?status=completed&modified_after=... oraz .../orders/{id}/refunds. Pull jest odporniejszy na zaginione webhooki, ale wymaga paginacji i własnego wskaźnika ostatniego pobrania.
Sam POST do MP nie może siedzieć w ścieżce zamówienia — jeśli Google zwróci 500, nie chcesz blokować płatności. Wzorzec: zdarzenie ląduje w tabeli kolejki (id, event_name, transaction_id, payload, status, attempts, next_attempt_at), cron co minut bierze paczkę np. 50 rekordów i wysyła. Retry z backoffem: 1, 5, 15, 60, 240 minut, po szóstej próbie status dead i alert mailem. api_secret trzymaj w zmiennej środowiskowej, nie w repozytorium; rotuj co 90–180 dni, a w trakcie rotacji utrzymuj dwa aktywne sekrety.
Loguj minimum: transaction_id, typ zdarzenia, kod HTTP odpowiedzi, pełny payload i czas. MP zwraca 204 bez treści, więc sam kod to za mało — bez payloadu nie odtworzysz, co poszło.
| Platforma | Zdarzenie | Punkt zaczepienia |
|---|---|---|
| PrestaShop | purchase | hook actionValidateOrder |
| PrestaShop | refund | actionOrderStatusPostUpdate + ID stanu „zwrócono” |
| WooCommerce | purchase | webhook order.completed |
| WooCommerce | refund | webhook refund.created lub GET /wp-json/wc/v3/orders/{id}/refunds |
| oba | dosyłka i audyt | cron czytający tabelę kolejki |
Zanim cokolwiek pojawi się w raportach, wyślij zdarzenie na endpoint walidacyjny /debug/mp/collect — zamiast /mp/collect, z tym samym measurement_id i api_secret. Odpowiedź zawiera validationMessages. Czytaj je dosłownie, punkt po punkcie. Jeśli komunikat mówi o nieznanej walucie, nie zgaduj, że „to pewnie nic nie znaczy” — popraw i wyślij ponownie. Walidacja wyłapuje rzeczy, których nie pokaże żaden inny ekran: literówki w nazwach parametrów, stringi w polach liczbowych, brak wymaganego currency.
Drugi krok to DebugView w GA4. To jedyne miejsce, w którym potwierdzasz, że zdarzenie w ogóle dotarło. Wymaga debug_mode: true w parametrach zdarzenia albo ?debug_mode=1. Uwaga: dane w DebugView żyją krótko — jeśli szukasz zdarzenia z zeszłego tygodnia, już go tam nie będzie. To narzędzie do testu tu i teraz, nie do audytu.
Trzeci krok to cierpliwość. Raporty standardowe pokazują dane częściowo po kilku godzinach, ale pełne przetworzenie potrafi zająć kilkadziesiąt godzin. Sprawdzanie raportu 20 minut po wdrożeniu i wyciąganie wniosków to najczęstszy sposób na niepotrzebną panikę i cofanie działającej integracji.
Ostateczna weryfikacja: eksport do BigQuery. W tabeli events_YYYYMMDD widzisz surowe zdarzenia bez agregacji i sprawdzisz dokładnie, co poszło: czy transaction_id się zgadza, czy items ma komplet pozycji, czy value jest liczbą. Zapytanie z UNNEST(event_params) to podstawa takiego sprawdzenia.
Test kontrolny na koniec: wyślij zdarzenie z celowo złym currency (np. PLN1) na endpoint debug i przeczytaj komunikat. Zapamiętaj jego treść — następnym razem rozpoznasz ten sam problem w logach w kilka sekund. Pamiętaj, że adres debug zawiera api_secret, więc nie wklejaj go do zgłoszeń i ticketów.
Objawy widać w raportach, przyczyny siedzą w payloadzie albo w kolejce. Zestawienie objaw–przyczyna–diagnoza:
| Objaw | Przyczyna | Metoda diagnozy |
|---|---|---|
| Przychód wyższy niż w sklepie | Duble purchase | Porównanie liczby wystąpień transaction_id |
| Brak pozycji w raportach produktowych | Brak items lub niepoprawne typy liczbowe | Sprawdzenie tablicy items w BigQuery i typów price, quantity |
| Zwroty nie pomniejszają przychodu | Inny transaction_id lub brak currency | Porównanie znak w znak z pierwotnym purchase |
| Zerowe sesje przy zdarzeniach z MP | Brak engagement_time_msec i session_id | Podgląd parametrów zdarzenia w DebugView |
| Zdarzenia nie pojawiają się wcale | Przekroczony limit 25 zdarzeń w request, payload powyżej 1 MB lub zły api_secret | Rozmiar i liczba zdarzeń w payloadzie, kod HTTP z kolejki |
Diagnozę dubli rób po stronie serwera, nie w GA4: SELECT transaction_id, COUNT(*) FROM mp_queue GROUP BY 1 HAVING COUNT(*) > 1. Jeśli wynik jest większy niż jeden, sprawdź, czy hook nie odpala się dwukrotnie (w PrestaShop zdarza się przy ponownej walidacji zamówienia) albo czy nie przyszła powtórka webhooka z WooCommerce. Wygodne zabezpieczenie: przed wysłaniem sprawdź, czy dla danej pary transaction_id i typu zdarzenia nie ma już rekordu ze statusem sent.
Typy danych to druga cicha pułapka. price jako string („49.99”) przejdzie przez request, ale wiersz w raporcie produktowym zostanie pusty. Pilnuj, żeby value, price i quantity były liczbami, a item_id niepuste i zgodne z identyfikatorami w sklepie.
Przy zwrotach najważniejsza jest dyscyplina: ten sam transaction_id i ta sama waluta co w purchase. Inaczej zdarzenie dotrze, ale nie skoryguje przychodu.
Wysyłanie purchase jednocześnie z gtag w przeglądarce i z serwera bez uzgodnionej deduplikacji.
Jak wykryć: Przychód w GA4 jest mniej więcej dwa razy wyższy niż liczba zamówień w sklepie lub ERP w tym samym okresie.
Jak naprawić: Ustal jedno źródło prawdy dla transakcji. Jeśli sklep już wysyła purchase w przeglądarce, z serwera wysyłaj tylko te zamówienia, których tag nie obsłużył (np. zamówienia telefoniczne, B2B, wpisy z ERP). Ten sam transaction_id w obu kanałach to warunek konieczny, ale nie wystarczający — decyzję o tym, kto wysyła zdarzenie, podejmij świadomie.
Brak client_id w body żądania.
Jak wykryć: Zdarzenie nie pojawia się w DebugView, mimo że endpoint zwraca kod 204. W Realtime widać ruch, ale zdarzenia nie da się przypisać do żadnej sesji.
Jak naprawić: Przekazuj client_id jawnie. Dla zamówień złożonych w przeglądarce odczytaj go z cookie _ga (dwa ostatnie segmenty, format GA1.1.XXXX.YYYY). Dla zamówień telefonicznych i z ERP wygeneruj własny, stabilny identyfikator i zapisz go przy zamówieniu, żeby był ten sam przy zakupie i przy zwrocie.
Pomijanie ga_session_id i engagement_time_msec w zdarzeniu z serwera.
Jak wykryć: Zdarzenie widać w Realtime, ale nie pojawia się w raportach zaangażowania, a sesje z tych zamówień są niedoszacowane.
Jak naprawić: Dodaj do params ga_session_id (z sesji, w której powstało zamówienie) oraz engagement_time_msec. Bez tych dwóch parametrów zdarzenie nie zostanie włączone do metryk zaangażowania w GA4. Jeśli zamówienie powstało poza przeglądarką, przyjmij to ograniczenie i nie licz na dane sesyjne.
value bez items albo items z price i quantity jako stringi.
Jak wykryć: W raporcie e-commerce widać przychód, ale brak danych produktowych. Albo odwrotnie: produkty są, a kwoty się nie sumują.
Jak naprawić: Zawsze wysyłaj items razem z value, a price i quantity przekazuj jako liczby, nie tekst w cudzysłowie. item_id musi być zgodny z tym, co wysyła przeglądarka, inaczej ten sam produkt rozjedzie się na dwa wiersze w raporcie.
Refund wysłany z innym transaction_id niż pierwotny zakup albo przed purchase.
Jak wykryć: Przychód w GA4 nie zmniejsza się po zaksięgowaniu zwrotu, mimo że request zwrócił 204.
Jak naprawić: Użyj dokładnie tego samego transaction_id, który poszedł z purchase. Kolejność też ma znaczenie: refund po purchase. Zwrot wysłany przed zakupem nie połączy się z transakcją i zostanie zignorowany.
Klucz api_secret wklejony do kodu JavaScript na stronie lub do repozytorium.
Jak wykryć: Sekret jest widoczny w źródle strony, w publicznym repo albo w narzędziu deweloperskim przeglądarki.
Jak naprawić: Sekret używaj wyłącznie po stronie serwera, w zmiennych środowiskowych. Jeśli wyciekł, wygeneruj nowy w GA4 i unieważnij stary. Jeden sekret nie powinien obsługiwać kilku środowisk jednocześnie.
Measurement Protocol rozwiązuje konkretny problem: transakcje i zwroty, które nigdy nie trafią do przeglądarki. Warunkiem jest poprawny client_id, ten sam transaction_id przy zakupie i zwrocie oraz liczbowe pola w items. Najdroższy błąd to podwójne naliczenie przychodu — dlatego najpierw ustal, kto wysyła purchase, a potem pisz kod. Zdarzenia z serwera testuj w DebugView i raz w miesiącu porównuj z danymi z ERP.
Nie. MP to uzupełnienie, nie zamiennik. Zdarzenia z serwera nie tworzą sesji, nie niosą informacji o źródle ruchu ani o zachowaniu użytkownika na stronie. Sensowny układ to tag w przeglądarce dla ruchu i zdarzeń interfejsu oraz MP dla transakcji, których przeglądarka nie widzi: zamówień z ERP, telefonicznych i B2B.
W GA4 przejdź do Admin, Data Streams, wybierz strumień, a następnie Measurement Protocol API secrets i utwórz nowy sekret. Nadaj mu nazwę, która pozwoli ci rozpoznać środowisko, np. sklep-produkcja. Sekret trzymaj wyłącznie na serwerze i pamiętaj, że po unieważnieniu starego klucza trzeba zaktualizować konfigurację.
Żądanie może zostać przyjęte, ale zdarzenie zostanie przypisane do losowego identyfikatora albo odrzucone. Efektem jest brak możliwości połączenia zakupu ze zwrotem i niespójne dane w raportach. Zawsze przekazuj client_id: z cookie _ga dla zamówień z przeglądarki, własny i zapisany w bazie dla zamówień telefonicznych oraz z ERP.
GA4 deduplikuje purchase po transaction_id, ale tylko wtedy, gdy identyfikator jest identyczny w obu zdarzeniach i wysyłany w tym samym okresie. W praktyce łatwo o rozjazd: inny format identyfikatora, dopisek z systemu ERP, inne zaokrąglenie kwoty. Bezpieczniej jest zdecydować, które zdarzenia obsługuje przeglądarka, a które serwer, i pilnować tego w kodzie.
Wyślij zdarzenie refund z tym samym transaction_id co zakup i przekaż w items wyłącznie zwracane pozycje, z ujemną quantity. Zwrot całego zamówienia to inny payload: możesz wysłać refund bez items, jeśli chcesz pomniejszyć całą transakcję. Jeden i drugi wariant daje inny efekt w raportach, więc wybierz wariant zgodny z tym, jak liczy zwroty twoje ERP.
Bo brakuje parametrów sesyjnych. Aby zdarzenie z serwera weszło do metryk zaangażowania, potrzebuje ga_session_id oraz engagement_time_msec. Jeśli zamówienie powstało poza przeglądarką, takich danych po prostu nie ma i nie należy ich wymyślać. Wtedy raportuj przychód, a nie sesje.
Możesz, ale z ograniczeniem: GA4 przyjmuje zdarzenia z timestamp_micros tylko w ograniczonym oknie czasowym wstecz. Jeśli zwroty księgujesz po tygodniach, część zdarzeń nie zostanie przyjęta. W takiej sytuacji potrzebna jest kolejka, szybka wysyłka po zaksięgowaniu w ERP albo osobne zestawienie korygujące, uzgodnione z osobą odpowiedzialną za raportowanie.
Jeśli chcesz sprawdzić, czy twoje purchase i refund w GA4 zgadzają się z zamówieniami w sklepie, napisz do nas — przejrzymy payloady i wdrożenie. Zajmujemy się PrestaShop, WooCommerce i integracjami z ERP.