Server-side tracking 18 września 2026 13 min czytania

Pinterest CAPI: dedup po samym event_id, choć tag i API mają różne nazwy zdarzeń

Tag Pinteresta zna addtocart, Conversions API tylko add_to_cart. Czy dedup w ogóle zadziała? Odpowiedź z kodu vendora, bo dokumentacja milczy.

Wdrażasz Pinterest Conversions API obok istniejącego tagu Pinteresta i trafiasz na coś, czego nie ma w żadnym poradniku. Tag w przeglądarce wysyła zdarzenie o nazwie addtocart. Conversions API przyjmuje wyłącznie add_to_cart. Pinterest każe deduplikować po event_id, ale Meta w tej samej sytuacji wymaga jeszcze zgodności nazwy zdarzenia. Jeśli Pinterest robi tak samo, to tag i serwer nigdy się nie sparują i cały lejek poza zakupem będzie liczony podwójnie.

Ten tekst powstał przy wdrożeniu Pinterest CAPI przez server-side GTM w sklepie meblowym na Shoperze, gdzie Pinterest daje kilka procent sesji i realne zakupy. Na zdarzeniu zakupu dedup działa i widać to w panelu: w pierwszej dobie po włączeniu toru serwerowego około 96 procent zdarzeń z API zostało sklejonych ze zdarzeniami tagu. Tylko że na zakupie obie strony przypadkiem nazywają zdarzenie tak samo (checkout), więc ten pomiar niczego nie mówi o add_to_cart.

Zamiast zgadywać, poszedłem do kodu: szablonu serwerowego Pinteresta, żywej biblioteki pixela, oficjalnej wtyczki WooCommerce współtworzonej przez Pinterest. Poniżej dowody, screeny z kodu i to, czego kod nadal nie rozstrzyga. Na końcu jest sposób, żeby domknąć pytanie pomiarem na własnym koncie, bez konta testowego.

Dlaczego to pytanie decyduje o istnieniu lejka

Overlap między tagiem a CAPI liczy się na parach zdarzeń. Para powstaje, gdy Pinterest uzna dwa zdarzenia z dwóch źródeł za to samo. Jeśli warunkiem parowania byłaby literalna zgodność event_name, to tag wysyłający addtocart i CAPI przyjmujące add_to_cart nigdy się nie sparują. Każde dodanie do koszyka liczyłoby się dwa razy, a wskaźnik jakości zdarzeń (EQS) spadłby zamiast wzrosnąć.

To nie jest problem teoretyczny. Jakość zdarzeń w Pintereście liczy overlap dla wszystkich typów zdarzeń, jakie wysyłasz, podobnie jak Meta liczy EMQ osobno dla każdego zdarzenia funnela. Dołożenie toru serwerowego, który nie skleja się z tagiem, obniża ocenę całego konta. Dlatego zanim dołożysz add_to_cart po stronie serwera, musisz wiedzieć, po czym Pinterest paruje. Mechanizm dedupu w lejku opisałem szerzej w tekście o deduplikacji zdarzeń funnela bez order_id. Tutaj chodzi o jedno konkretne pytanie: czy nazwa musi się zgadzać.

Dwa słowniki nazw, jedno zdarzenie

Zacznijmy od faktu, który wszystko uruchamia. Tag Pinteresta i Conversions API mają dwa różne słowniki nazw. Pixel zna nazwy bez podkreśleń, API zna nazwy z podkreśleniami. Zestawiłem obie listy obok siebie: lista pixela pochodzi z żywej biblioteki main.e47a302d.js, lista API z enum w szablonie serwerowym Pinteresta.

Tabela zestawiająca nazwy zdarzeń tagu Pinteresta i Conversions API: 8 nazw identycznych, 12 różniących się wyłącznie podkreśleniami, 3 bez odpowiednika

Bilans: 8 nazw identycznych po obu stronach, 12 różniących się wyłącznie podkreśleniami, 3 bez odpowiednika (externalmeasurement istnieje tylko w pixelu, app_install i app_open tylko w API). Dla tej dwunastki, w której są add_to_cart, page_visit i initiate_checkout, nie istnieje użyteczna konfiguracja z identycznym stringiem po obu stronach. Za chwilę pokażę, dlaczego słowo „użyteczna” ma tu znaczenie.

Gdyby dedup wymagał literalnej zgodności nazw, Pinterest sprzedawałby deduplikację działającą dla 8 typów zdarzeń i niedziałającą dla 12, bez słowa o tym w dokumentacji, przy jednoczesnym zaleceniu “dual integration”: wysyłaj te same zdarzenia przez tag i przez API. To pierwszy argument. Argument z niemożliwości. Ale sam w sobie jest wnioskowaniem, nie dowodem, więc idziemy do kodu.

Dowód 1: szablon serwerowy Pinteresta tłumaczy nazwy

Pinterest utrzymuje na GitHubie własny szablon tagu dla server-side GTM (pinterest/ss-gtm-template). W środku jest stała EVENT_NAME_MAPPINGS, która tłumaczy nazwy przychodzące z warstwy danych na słownik API.

Fragment template.tpl z repozytorium pinterest/ss-gtm-template: stała EVENT_NAME_MAPPINGS tłumaczy addtocart na add_to_cart, pagevisit na page_visit, view_item na page_visit, begin_checkout na initiate_checkout; linia 805 stosuje mapę

Dwie rzeczy w tej tabeli. Po pierwsze, są tam nazwy pixela: addtocart idzie na add_to_cart, pagevisit na page_visit, viewcategory na view_category. Pinterest we własnym kodzie zakłada, że zdarzenie nazwane w konwencji tagu ma trafić do API pod inną nazwą. Po drugie, są tam nazwy GA4: view_item na page_visit, begin_checkout na initiate_checkout, view_item_list na view_category. To jest oficjalna opinia Pinteresta o tym, jak mapować warstwę danych w konwencji GA4, którą ma większość sklepów.

Linia 805 domyka sprawę: pinterestEventName = EVENT_NAME_MAPPINGS[gtmEventName] || gtmEventName. Nazwa jest tłumaczona. event_id w tym szablonie nie jest przetwarzany w ogóle, idzie do payloadu tak, jak przyszedł.

Uczciwie: to dowód o tym, co robi nadawca. Nie mówi wprost, jak strona odbierająca skleja addtocart z tagu z add_to_cart z API. Dlatego potrzebne są kolejne.

Dowód 2: biblioteka pixela nie zna nazw z podkreśleniami

Szablony można obejść, więc poszedłem do źródła, od którego szablony nie zależą: żywej biblioteki pixela ładowanej z s.pinimg.com. To można powtórzyć w minutę, bez żadnego konta.

Sesja bash: pobranie core.js Pinteresta, wyciągnięcie nazwy biblioteki main.e47a302d.js, liczniki wystąpień: addtocart 2, pagevisit 2, add_to_cart 0, page_visit 0, view_category 0, initiate_checkout 0, oraz stała EVENT_ID_PARAM_KEYS

W 139 kilobajtach biblioteki nie ma ani jednego stringa w stylu API. Zero wystąpień add_to_cart, page_visit, view_category, initiate_checkout. Jest za to zamknięta lista SUPPORTED_EVENT_NAMES z 21 nazwami bez podkreśleń i stała EVENT_ID_PARAM_KEYS, czyli trzy klucze, pod którymi biblioteka przyjmuje identyfikator zdarzenia: event_id, eid, eventID.

Droga ucieczki istnieje, ale jest samobójcza

Pierwsza wersja tego argumentu była za mocna. Twierdziłem, że tag „fizycznie nie umie wysłać add_to_cart”. Nieprawda. Biblioteka nie odrzuca nazwy spoza listy. Loguje ostrzeżenie i wysyła ją dalej.

Sformatowany fragment biblioteki pixela: nazwa spoza SUPPORTED_EVENT_NAMES daje console.warn z tekstem "conversion reporting will not be available", po czym s.event = e wysyła ją dalej; niżej stałe EVENT_ID_PARAM_KEYS i SUPPORTED_EVENT_NAMES

Tylko że ta sama linia mówi wprost: “You may use it to build audiences, but conversion reporting will not be available”. Możesz więc ujednolicić nazwy i wysyłać z tagu add_to_cart, ale takie zdarzenie przestaje liczyć się do konwersji. Czyli „naprawa” przez ujednolicenie nazw, którą podpowiadają streszczenia z wyszukiwarki, wyłącza raportowanie konwersji po stronie tagu. Poprawnie brzmi to tak: nie ma użytecznej konfiguracji ze zgodnymi nazwami, a nie „nie ma żadnej”.

Dowód 3: NoCodeCAPI, czyli jak sam pixel projektuje dedup

W tej samej bibliotece siedzi moduł NoCodeCAPI. To produkt Pinteresta, w którym sam pixel wysyła zdarzenie w kształcie Conversions API, równolegle do zwykłego hitu tagu. Funkcja setupPayload buduje ten payload.

Sformatowana funkcja setupPayload z modułu NoCodeCAPI: wysyłka bramkowana obecnością event_id pod jednym z kluczy EVENT_ID_PARAM_KEYS, payload z event_name wziętym dosłownie z wywołania, action_source, event_time i event_id

Trzy rzeczy z tego kodu. Po pierwsze, event_name: n to nazwa wzięta dosłownie z wywołania pintrk('track', ...), czyli w stylu pixela (addtocart), bez żadnego mapowania, choć payload ma kształt API (action_source, event_time, user_data, custom_data). Po drugie, wysyłka jest bramkowana obecnością event_id: bez identyfikatora funkcja nie zwraca payloadu i nic nie leci. To Pinterest projektuje deduplikację wokół event_id, nie wokół nazwy. Po trzecie, ten produkt z definicji duplikuje zdarzenie: ta sama przeglądarka wysyła hit pixela i zdarzenie w kształcie API. Gdyby backend nie sklejał nazwy w stylu pixela z odpowiednikiem API, NoCodeCAPI liczyłby podwójnie każde zdarzenie każdego sprzedawcy, który go włączył.

To pierwszy dowód, który mówi o stronie odbierającej, a nie tylko o nadawcy. Z zastrzeżeniem: endpoint NoCodeCAPI jest konfigurowalny i nie jest dowiedzione, że to ten sam ingest co publiczne API v5. Dowód jest mocny, nie rozstrzygający.

Dowód 4: własna wtyczka Pinteresta robi dokładnie to, o co pytamy

Najmocniejszy dowód pochodzi z produkcyjnego kodu, który działa na wielu tysiącach sklepów. Wtyczka pinterest-for-woocommerce (wersja stabilna 1.5.0) ma w nagłówku trzech autorów: Automattic, Pinterest, WooCommerce. Rozpakowałem ją i prześledziłem łańcuch ogniwo po ogniwie.

Łańcuch czterech fragmentów kodu wtyczki pinterest-for-woocommerce: stała EVENT_ADD_TO_CART równa AddToCart dla pixela, mapa EVENT_MAP tłumacząca ją na add_to_cart dla Conversions API, pętla track_event podająca ten sam obiekt $data obu trackerom, komentarz autorów o deterministycznym event_id dla deduplikacji

Łańcuch wygląda tak. Stałe nazw zdarzeń są w konwencji pixela: 'AddToCart', 'PageVisit', 'Checkout'. Klasa Tag wstawia tę nazwę dosłownie do pintrk('track', 'AddToCart', ...). Biblioteka pixela normalizuje ją przez trim().toLowerCase(), więc na drucie jest addtocart. Równolegle klasa Tracker ma mapę EVENT_MAP z komentarzem “maps tracking events to corresponding tracker methods and conversions API events names”, która tłumaczy AddToCart na add_to_cart. Metoda track_event przechodzi w pętli po wszystkich trackerach (pixel i API) i podaje każdemu ten sam obiekt $data, który niesie jeden event_id.

Czyli z tej samej wtyczki, z tym samym event_id, do pixela idzie addtocart, a do API add_to_cart. Trzy z pięciu typów zdarzeń w tej wtyczce mają różne nazwy po obu stronach. Zgodne są tylko checkout i search. Gdyby dedup wymagał literalnej zgodności nazw, własna wtyczka Pinteresta liczyłaby podwójnie PageVisit, AddToCart i ViewCategory na każdym sklepie WooCommerce, który ją ma, od wersji 1.4.0. Przy jednoczesnych komentarzach w kodzie mówiących, że event_id jest tam “as a requirement by Pinterest for deduplication purposes”.

Zastrzeżenie, którego nie pomijam: cytaty ze słowem “deduplication” są w tej wtyczce przypięte do zdarzenia Checkout, czyli akurat do tego, którego nazwy są zgodne. Dla AddToCart mechanizm w kodzie jest identyczny, ale osobnego zdania o dedupie przy AddToCart tam nie ma.

Co mówi dokumentacja, a co mówią streszczenia AI

Wszystko powyżej byłoby niepotrzebne, gdyby Pinterest napisał jedno zdanie. Nie napisał. Zestawiłem dosłowne cytaty.

Cztery karty z cytatami: dokumentacja Pinteresta wymaga identycznego event_id bez warunku na nazwę, pracownik Pinteresta na forum mówi o samym event_id, dokumentacja Mety wymaga zgodności eventID i event oraz event_name, a streszczenie AI przypisuje Pinterestowi regułę Mety, której nie ma na developers.pinterest.com

Dokumentacja Pinteresta w sekcji “Prevent event duplication” każe wysyłać event_id i event_name ze wszystkich źródeł, ale zdanie definiujące warunek dopasowania mówi wyłącznie o identyfikatorze: “that event_id must be the same in both instances for Pinterest to regard them as duplicates”. Warunku na event_name nie ma w żadnym zdaniu. Pracownik Pinteresta na forum deweloperskim, pytany o rozjazd event_id między tagiem a API, odpowiada: “We do not modify or process event_id used by client in any source of TAG, CAPI or GTM”. Też tylko o identyfikatorze.

Meta pisze inaczej i wprost: pixelowy eventID musi zgadzać się z event_id, a pixelowy event z event_name. Oba warunki naraz. I tu jest pułapka metodyczna warta osobnego akapitu. Streszczenia generowane nad wynikami wyszukiwania wielokrotnie twierdziły, że Pinterest wymaga “non-empty, non-null event_id and event_name” do deduplikacji. Sprawdziłem grepem na surowym HTML wszystkich pobranych stron developers.pinterest.com i help.pinterest.com: fraza nie występuje nigdzie. To sformułowanie pochodzi z dokumentacji Meta Conversions API. Warstwa AI nad wyszukiwarką przypisała Pinterestowi regułę Mety. Jeśli oprzesz na tym wdrożenie, ujednolicisz nazwy i wyłączysz raportowanie konwersji po stronie tagu, jak w dowodzie 2. O tym, jak Meta faktycznie liczy pary, pisałem przy okazji Meta CAPI na Shoperze bez wtyczek.

Co to znaczy dla wdrożenia

Werdykt po zestawieniu wszystkich dowodów: kluczem dedupu jest event_id, nazwa jest tłumaczona po stronie Pinteresta. Pewność: bardzo prawdopodobne, rozstrzygnięte praktyką własną vendora, bez zdania wprost w dokumentacji. Ryzyko resztkowe istnieje i domyka się je pomiarem, nie założeniem. Stąd trzy konsekwencje dla kolejności prac.

Najpierw event_id w tagu web, potem serwer

Overlap liczy się na parach. Bez event_id w tagu przeglądarkowym overlap jest strukturalnie zerowy, niezależnie od tego, jak dobrze zbudujesz tor serwerowy. Kolejność prac to web, potem serwer, nie odwrotnie. Identyfikator generujesz wyłącznie w przeglądarce, w formacie łączącym nazwę zdarzenia z losowym sufiksem, i ten sam string przekazujesz do tagu (pod jednym z trzech kluczy, które zna biblioteka) i do zdarzenia serwerowego. W lejku nie używaj transaction_id jako identyfikatora, bo szablony serwerowe zamieniają go na event_id zakupu. Zakup ma własny, deterministyczny mechanizm, opisany w trzech sposobach na deduplikację purchase.

add_to_cart jako eksperyment, który odblokowuje resztę

add_to_cart przestaje być „najbezpieczniejszym pierwszym krokiem” i staje się najtańszym testem hipotezy. W tym sklepie to około półtora tysiąca zdarzeń na dobę zamiast kilkudziesięciu tysięcy przy page_visit. Jeśli dedup działa, licznik po deduplikacji nie rośnie proporcjonalnie do dołożonego źródła. Jeśli nie działa, widać podwojenie, cofasz jedną zmianę i nie postawiłeś na tę kartę dziesiątek tysięcy zdarzeń dziennie. Zakup zostaje nietknięty przez cały eksperyment.

Kontrola negatywna rozstrzyga bez konta testowego

Tryb testowy Pinteresta się do tego nie nadaje: zdarzenia z parametrem testowym nie są zapisywane, więc nie da się nimi zmierzyć dedupu. Zamiast tego w tym samym wydaniu co add_to_cart wchodzi CAPI dla lead, gdzie nazwa jest identyczna po obu stronach i wolumen jest znikomy.

Dwie karty: eksperyment addtocart w tagu i add_to_cart w API z tym samym event_id oraz kontrola negatywna lead i lead; poniżej macierz trzech odczytów overlapu i ich interpretacja

Odczyt overlapu per typ zdarzenia daje wtedy rozstrzygnięcie. lead ma overlap, add_to_cart nie ma: nazwy muszą być zgodne literalnie i lejek poza zdarzeniami o wspólnej nazwie jest bezcelowy. Oba mają overlap: werdykt potwierdzony na koncie. Żadne nie ma: błąd jest w konstrukcji pary po naszej stronie, nie w nazwach. Bez tej kontroli overlap zero jest nieodróżnialny od źle zbudowanej pary.

Odczyt bierzesz z endpointu jakości zdarzeń (conversion_eqs), z pola external_event_id.[typ].overlap, doba po dobie. Pierwszy wiarygodny odczyt nie wcześniej niż 96 godzin po publikacji, bo okno dedupu Pinteresta to 48 godzin, a raport dojrzewa kolejną dobę. Nie mierz „liczby dubli po 48 h”: w tym oknie dane są niedojrzałe, a jeśli w tym samym wydaniu zmieniasz cokolwiek w tagu zasilającym ten sam licznik, mierzysz dwa efekty o przeciwnych znakach naraz. Podobne pułapki dojrzewania raportów opisałem przy ukrytym duplikacie purchase na Shoperze.

Czego kod nadal nie rozstrzyga

Dla porządku, lista tego, co pozostaje otwarte po całym researchu:

  • Dokumentacja Pinteresta nie ma zdania wprost „dedup działa mimo różnych event_name”. Nie ma też zdania przeciwnego.
  • Dowód z NoCodeCAPI dotyczy konfigurowalnego endpointu i nie jest dowiedzione, że to ten sam ingest co publiczne API v5.
  • Cytaty o deduplikacji we wtyczce WooCommerce są przypięte do Checkout, gdzie nazwy akurat się zgadzają.
  • Nie znalazłem relacji praktyka z realnym testem dokładnie tego scenariusza: addtocart w tagu, add_to_cart w API, ten sam event_id, odczyt overlapu.

Dlatego pomiar na add_to_cart z kontrolą negatywną na lead nie jest ostrożnością na wszelki wypadek. To jedyny sposób, żeby zamienić „bardzo prawdopodobne” na „potwierdzone na tym koncie”. Gdy odczyt spłynie, uzupełnię ten tekst.

Tor serwerowy dla Pinteresta buduję w ramach tego samego wdrożenia co Google Ads i Meta, na wspólnym kontenerze server-side, którego architekturę dla Shopera opisuje kompletny przewodnik po server-side GTM. Zakres i ceny są na stronie usług i pakietów, a jeśli chcesz najpierw sprawdzić, jakie tagi i tory serwerowe ma dziś Twój sklep, przepuść go przez checker trackingu.

Podsumowanie

Tag Pinteresta i Conversions API mają dwa słowniki nazw: 12 z 23 typów zdarzeń różni się wyłącznie podkreśleniami, w tym add_to_cart i initiate_checkout. Nie da się ich ujednolicić bez wyłączenia raportowania konwersji po stronie tagu, bo biblioteka pixela nazwy spoza swojej listy przepuszcza z ostrzeżeniem “conversion reporting will not be available”.

Cztery niezależne dowody z kodu Pinteresta i jego partnerów mówią to samo: szablon serwerowy tłumaczy nazwy i nie rusza event_id, biblioteka pixela nie zna nazw z podkreśleniami, moduł NoCodeCAPI bramkuje wysyłkę obecnością event_id i wysyła nazwę bez mapowania, a wtyczka WooCommerce współtworzona przez Pinterest podaje ten sam event_id do tagu pod nazwą AddToCart i do API pod nazwą add_to_cart. Dokumentacja definiuje warunek dopasowania wyłącznie przez event_id. Reguła „event_id i event_name”, którą streszczenia AI przypisują Pinterestowi, pochodzi z dokumentacji Mety.

Wdrożeniowo: event_id najpierw w tagu web, add_to_cart jako pierwszy eksperyment zamiast page_visit, kontrola negatywna na lead w tym samym wydaniu, odczyt overlapu per typ z endpointu jakości nie wcześniej niż po 96 godzinach. Hipoteza jest bardzo prawdopodobna, ale to pomiar ją domyka, nie kod.

Porozmawiajmy o Twoim trackingu

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