Server-side tracking 19 czerwca 2026 12 min czytania

Zamówienia za pobraniem znikają z pomiaru server-side - jak liczyć COD bez fałszywych zakupów

Pobranie nie generuje płatności, więc webhook order.paid nie odpala, a pomiar server-side gubi zamówienia COD. Jak złapać je na order.create, nie licząc porzuconych koszyków.

Zamówienia za pobraniem to w polskim e-commerce nie margines, tylko 20-40% koszyka, zależnie od branży. Zachodni content o trackingu traktuje COD jakby nie istniał, bo na tamtych rynkach prawie go nie ma. Efekt jest taki, że większość wdrożeń server-side ma w pomiarze dziurę dokładnie pod tym jednym typem płatności, a nikt jej nie widzi, bo średnia wygląda znośnie.

Problem jest strukturalny, nie konfiguracyjny. Pomiar server-side dla zakupu zwykle wpina się w zdarzenie płatności. Na Shoperze to webhook order.paid, w innych silnikach analogiczny event “zamówienie opłacone”. Pobranie nie ma momentu płatności na stronie - klient płaci kurierowi przy odbiorze. Więc order.paid dla zamówienia COD nigdy nie odpala, a serwer, który nasłuchuje tylko tego zdarzenia, jest na pobrania całkowicie ślepy.

W tym artykule pokazuję, jak domknąć tę dziurę: złapać COD na osobnym webhooku order.create, forwardować z niego wyłącznie pobrania (online zostawić dla order.paid), rozpoznawać metodę odpornie na zmianę nazwy i nie policzyć niczego dwa razy. Całość na bazie realnego wdrożenia dla sklepu na Shoper Premium RWD, gdzie pobranie to około 15% zamówień. Wartości zanonimizowane, mechanizm dokładnie taki, jak poszedł na produkcję.

Dlaczego pobranie nie odpala pomiaru server-side

Server-side dla Purchase opiera się na jednym założeniu: jest jakieś serwerowe zdarzenie, które mówi “zamówienie zostało opłacone”, i to ono wyzwala konwersję. Na nim wisi cała odporność, o którą chodzi w server-side. To dlatego konwersja z webhooka płatności przeżywa adblock, ITP i zamkniętą kartę - bo nie zależy od tego, czy przeglądarka zdążyła cokolwiek wysłać.

Pobranie tego momentu nie ma. Zamówienie COD powstaje, klient kończy checkout, ale płatność dzieje się dni później, fizycznie, u kuriera. Z perspektywy sklepu nie ma zdarzenia “opłacone”, więc order.paid się nie wywołuje. Konwersja server-side, która czeka na ten webhook, dla pobrania po prostu nie nadejdzie - nigdy.

Co zostaje? Tylko piksel w przeglądarce na stronie podziękowania. Czyli dokładnie ten najsłabszy kanał, dla którego server-side w ogóle wdrażaliśmy - ten, który gubi 40-50% zdarzeń przez adblock, iOS i ITP. Dla zamówień online walczysz przynajmniej o powrót z bramki płatności wzorcem capture and replay. Przy pobraniu problem jest głębszy: nie ma czego ratować po stronie serwera, bo serwer nigdy nie dostał sygnału.

Praktyczny skutek: jeśli pobranie to 15% zamówień, to 15% sprzedaży istnieje w pomiarze wyłącznie jako kruchy hit z przeglądarki. To zaniża pokrycie Purchase, rozjeżdża proporcję browser do server i karmi Smart kampanie obrazem, w którym pewna grupa klientów konwertuje “słabiej”, choć w rzeczywistości po prostu jej nie liczymy w solidnym kanale.

Pomysł: drugi webhook na order.create

Zdarzenie, które odpala się dla każdego zamówienia, niezależnie od metody płatności, to order.create - moment utworzenia zamówienia. Pobranie też je generuje. Więc dokładamy drugi webhook, na order.create, kierujący na osobny endpoint. Istniejący order.paid zostaje nietknięty i dalej obsługuje online.

I tu pojawia się odruch, który psuje wszystko: skoro order.create łapie każde zamówienie, to może forwardować z niego po prostu wszystko? Nie. order.create odpala się, zanim cokolwiek zostało zapłacone. Gdyby forwardować każde utworzone zamówienie jako Purchase, liczyłbyś też te online, które klient porzuci na bramce i nigdy nie opłaci. Do Meta poszłyby fałszywe zakupy, a Smart Bidding dostałby zatruty sygnał - uczyłby się na transakcjach, których nie ma.

Dlatego reguła jest prosta i twarda: z order.create forwardujemy wyłącznie pobrania. Zamówienia online na tym webhooku są świadomie pomijane i doliczą się później, gdy faktycznie zostaną opłacone i odpali się dla nich order.paid. Dwa czyste tory:

Typ płatnościWebhookKiedy liczone
Pobranie (COD)order.createprzy utworzeniu zamówienia
Online (BLIK, przelew, karta)order.paiddopiero po opłaceniu

W realnym wdrożeniu widać to wprost w logu: zamówienia online trafiające na order.create zostawiają wpis “pominięto, nie pobranie”, i tych jest w oknie obserwacji kilka razy więcej niż złapanych pobrań. To nie szum - to dowód, że brama działa i nie przepuszcza online jako fałszywych zakupów.

Pułapka 1: rozpoznawaj pobranie po ID metody, nie po nazwie

Żeby z order.create puścić tylko COD, trzeba zamówienie sklasyfikować: pobranie czy online. Naturalny odruch to dopasowanie po nazwie metody płatności - regex na “pobranie”, “przy odbiorze”, “za pobraniem”. Działa w dniu wdrożenia i jest pułapką.

Nazwa metody płatności to pole, które klient edytuje w panelu. Pewnego dnia ktoś zmieni “Płatność przy odbiorze” na “Płatność przy odbiorze (kurier DPD)” albo doda wariant “Pobranie - paczkomat”, i Twój regex cicho przestaje łapać część pobrań. Żadnego błędu, żadnego alertu - po prostu od tego dnia kawałek COD znowu znika z pomiaru, a zauważysz to za miesiąc, kiedy będziesz się zastanawiał, czemu pokrycie spadło.

Stabilny jest identyfikator metody płatności, nie jej nazwa. Zmiana nazwy w panelu nie rusza ID. Więc klasyfikacja idzie po liście ID:

  • ID metody na liście pobraniowej (np. 18 dla “Płatność przy odbiorze”) - forward jako COD.
  • ID metody na liście online (np. 3 dla płatności online) - pomiń na order.create, poczekaj na order.paid.
  • Dopasowanie po nazwie zostaje wyłącznie jako fallback, gdy lista ID jest pusta.

Najważniejszy jest trzeci przypadek: ID, którego nie ma na żadnej liście. Sklep dodaje nową metodę płatności i jej ID nie jest ani pobraniowe, ani online. Kuszące jest zgadnąć - “pewnie online, pominę”. Tego nie wolno. Nieznana metoda to ślepa plama, o której chcesz wiedzieć, a nie którą chcesz po cichu zakwalifikować. Dlatego nieznane ID wyzwala alert (“nieznana metoda płatności”) i bezpiecznie pomija forward, zamiast strzelać w ciemno. Lepiej świadomie nie policzyć jednego zamówienia i to naprawić, niż wysłać serię fałszywych zakupów albo na odwrót zgubić cały nowy kanał.

Jak znaleźć ID metod płatności

Klasyfikacja po ID zakłada, że te ID znasz. Na Shoperze masz dwie drogi. W panelu administracyjnym, w konfiguracji metod płatności, identyfikator widać w adresie URL przy edycji danej metody. Pewniejsze jest jednak odpytanie API zamówień: w danych zamówienia metoda płatności siedzi jako para ID plus nazwa, więc wystarczy pobrać kilkanaście ostatnich zamówień różnymi metodami i wypisać unikalne pary. To samo robi skrypt diagnostyczny, który przy wdrożeniu listuje wszystkie metody i podpowiada kandydatów na pobranie.

Wynik tej inwentaryzacji to dwie krótkie listy: ID pobraniowe i ID online. Zapisujesz je raz w konfiguracji i od tego momentu zmiana nazwy w panelu nic nie psuje. Jedyny moment, gdy trzeba je ruszyć, to dodanie nowej metody płatności - i właśnie po to jest alert o nieznanym ID, żeby ten moment nie przeszedł niezauważony.

Pułapka 2: nie policz tego samego zamówienia dwa razy

Skoro online idzie przez order.paid, a COD przez order.create, trzeba mieć pewność, że żadne zamówienie nie wpadnie oboma drogami. Brzegowy przypadek istnieje: błąd klasyfikacji, zmiana statusu, zamówienie, które najpierw wygląda jak jedno, potem jak drugie.

Zabezpieczenie jest na poziomie huba: idempotencja po numerze zamówienia. Numer, który już został sforwardowany, nie jest forwardowany drugi raz - kolejne wystąpienie ląduje w logu jako “duplikat” i to jest stan normalny, nie błąd. Dzięki temu, nawet jeśli to samo zamówienie z jakiegoś powodu trafi i na create, i na paid, do Meta poleci dokładnie raz.

Do tego dochodzi druga warstwa deduplikacji, już po stronie Meta. Serwerowy Purchase dla pobrania dostaje ten sam event_id co hit z przeglądarki - budowany deterministycznie z numeru zamówienia, czyli purchase_ plus numer. Meta zestawia zdarzenie z piksela ze zdarzeniem z serwera po tym wspólnym event_id i traktuje je jako jedno. To ten sam mechanizm, który opisałem dla całego lejka w deduplikacji zdarzeń server-side bez order_id, a dla samego zakupu w trzech sposobach na dedup Purchase na Shoperze. Przy pobraniu hit z przeglądarki bywa ten kruchy, ale gdy oba dotrą, dublują się czysto.

Pułapka 3: trwała kolejka i event_time z momentu zamówienia

Forward do Meta potrafi się nie udać - chwilowy błąd API, sieć, timeout. Gdyby COD forwardować raz, w locie, i porzucać przy błędzie, część pobrań ginęłaby tym razem nie przez brak webhooka, tylko przez nieudaną wysyłkę. Dlatego zamówienie wchodzi do trwałej kolejki, a osobny przebieg co jakiś czas zgarnia nieforwardowane i ponawia. Pobranie, które nie poszło za pierwszym razem, dośle się przy kolejnej próbie, zamiast zniknąć.

Druga rzecz, którą łatwo przeoczyć, to event_time. Stempel zdarzenia musi pochodzić z momentu, w którym hub przyjął zamówienie, a nie z momentu forwardu. To istotne właśnie przy ponawianiu: jeśli retry wyśle zdarzenie kilka godzin później ze stemplem “teraz”, atrybucja się rozjedzie, a w skrajnym przypadku Meta odrzuci event, bo okno przyjmowania to siedem dni od faktycznego zdarzenia. Stempel z chwili przyjęcia zamówienia trzyma czas spójnie niezależnie od tego, kiedy poszedł forward.

Co to realnie zmienia

Dla opisywanego sklepu pobranie to około 15% zamówień. Przed wdrożeniem te 15% istniało w pomiarze wyłącznie jako hit z przeglądarki - czyli realnie sporo mniej, bo to ten kanał, który najwięcej gubi. Po wdrożeniu każde zamówienie COD dostaje serwerowego bliźniaka z tym samym event_id, odpornego na adblock i zamkniętą kartę, dokładnie tak jak online.

Skutek na pokryciu Purchase: kanał serwerowy przestaje mieć systematyczną dziurę pod jednym typem płatności. Pokrycie, które wcześniej zatrzymywało się poniżej pełni właśnie dlatego, że pobrania nie miały serwerowego źródła, ma teraz komplet.

Po wdrożeniu weryfikacja jest prosta i warto ją zrobić w pierwszych 24-48 godzinach:

  • W logu pojawiają się wpisy “wykryto pobranie” (COD poszło) oraz “pominięto, nie pobranie” dla online na order.create.
  • Numery zamówień COD mają w rejestrze status sforwardowany.
  • W Events Managerze, w widoku zdarzeń Purchase, liczba z serwera rośnie ku liczbie z przeglądarki, a flaga deduplikacji się poprawia - te same event_id browser i server.
  • Kontrola braku podwójnego liczenia: żadne order_id online nie ma dwóch forwardów; “duplikat” w logu to norma, nie błąd.

Warto też raz na jakiś czas zerknąć na alerty o nieznanej metodzie płatności. To Twój wczesny sygnał, że sklep dodał coś nowego i trzeba dopisać ID do właściwej listy, zanim nowa metoda zacznie znikać z pomiaru.

Podsumowanie

Pobranie jest niewidoczne dla pomiaru server-side z prostego, strukturalnego powodu: nie ma momentu płatności, więc webhook order.paid nigdy nie odpala, a serwer wpięty w to zdarzenie nie ma czego liczyć. Domknięcie tej dziury to drugi webhook na order.create, z którego forwardujemy wyłącznie pobrania, podczas gdy online dalej czeka na order.paid.

Cztery rzeczy decydują, czy to zadziała, czy cicho się rozsypie. Klasyfikacja po ID metody, nie po nazwie, bo nazwę klient zmieni i nie dowiesz się o tym. Alert na nieznane ID zamiast zgadywania, bo nowa metoda to ślepa plama, nie szczegół. Idempotencja po numerze zamówienia plus wspólny event_id, żeby nic nie policzyło się dwa razy. I trwała kolejka ze stemplem z momentu zamówienia, żeby nieudany forward dało się ponowić bez psucia atrybucji.

Efekt: kawałek sprzedaży rzędu kilkunastu procent, który wcześniej istniał tylko jako kruchy hit z przeglądarki, dostaje pełny, odporny kanał serwerowy. Jeśli chcesz mieć pomiar pobrań ułożony tak na własnym sklepie, zaczynam zawsze od czystego server-side w warstwie motywu Shoper Storefront - szczegóły na stronie Tracking Shoper.

Porozmawiajmy o Twoim trackingu

30 minut bez zobowiązań. Bez sesji sprzedażowej. Powiem czy mogę pomóc i co realnie da się odzyskać.