Wszystkie wpisy

14 czerwca 2026

Przywracanie fbc/fbp dla zakupu offline — atrybucja konwersji bez przeglądarki w Meta CAPI

fbc (Facebook click ID) i fbp (Facebook browser ID) to parametry, po których Meta dopasowuje zdarzenie do użytkownika i kampanii. Powstają w przeglądarce — więc konwersja wygenerowana offline ich nie ma. Zamówienie domknięte telefonicznie przez obsługę albo dosprzedaż z systemu płatności przychodzi do sGTM webhookiem z backendu, bez przeglądarki, a więc bez fbc/fbp. Efekt: konwersje offline trafiają do Meta Conversions API ze słabym dopasowaniem zdarzeń.

To rozszerzenie podejścia, które opisałem przy wzbogacaniu page_view e-mailem — tylko trudniejsze, bo spina dwa zdarzenia rozdzielone w czasie i w kanale. Rozwiązanie: gdy użytkownik jest online — zapisać fbc/fbp do Stape Store; gdy później przychodzi webhook zakupu — przywrócić je z magazynu i dokleić do zdarzenia Purchase.

Architektura konwersji offline — dwie ścieżki

Mechanizm ma dwie ścieżki spięte tą samą zmienną reStore i tym samym identyfikatorem użytkownika.

ŚCIEŻKA ONLINE — zapis
page_view
fbc/fbp z cookies
reStore
RECORD
📦 Store
fbc/fbp pod user_id
ŚCIEŻKA OFFLINE — odczyt
webhook purchase
bez fbc/fbp
reStore
RESTORE
Meta — Purchase
CAPI, z fbc/fbp
Linchpinem obu ścieżek jest identyfikator użytkownika. Musi mieć tę samą wartość online i w webhooku — inaczej zapis i odczyt trafią na różne dokumenty.

Zaletą reStore (w odróżnieniu od pary Writer + Lookup z poprzedniego use case) jest to, że jest dwukierunkowy: gdy dane są obecne — zapisuje, gdy ich brak — przywraca. Ta sama zmienna obsługuje obie ścieżki.

Warunek krytyczny: identyfikator musi łączyć online i offline

To miejsce, w którym najłatwiej się wyłożyć. Pokusa jest taka, żeby użyć stape_user_id (z nagłówka x-stape-user-id) jako jedynego klucza — tak jak przy wzbogacaniu page_view. Tu to nie zadziała, bo stape_user_id jest liczony per żądanie z atrybutów nadawcy (IP, User-Agent, TLS):

  • w sesji online żądanie wysyła przeglądarka użytkownika → identyfikator użytkownika,
  • offline’owy webhook wysyła serwer sklepu / systemu płatności → identyfikator serwera, nie użytkownika.

Te wartości się różnią, więc odczyt na webhooku nie trafi na dokument zapisany online. Most się nie buduje.

Rozwiązaniem jest drugi identyfikator, obecny po obu stronach — wewnętrzne user_id zalogowanego klienta, przekazywane też w payloadzie webhooka. reStore przyjmuje listę identyfikatorów z fallbackiem, więc stape_user_id zostaje dla dopasowań online↔online, a user_id realnie spina online z offline.

IdentyfikatorCo spinaOgraniczenie
stape_user_idonline ↔ online (ta sama przeglądarka)per żądanie → nie przechodzi do webhooka z serwera
user_idonline ↔ offline (webhook)działa tylko dla zalogowanych sesji

Drugi warunek to timing: identyfikator pomostowy musi być obecny już w chwili zapisu fbc/fbp (na page_view), nie dopiero przy zakupie. Inaczej profil zaindeksuje się tylko po stape_user_id i mostu nie ma.

Który identyfikator wybrać

To hierarchia stabilności, nie wybór zero-jedynkowy:

IdentyfikatorStabilnośćUwagi
Wewnętrzne user_id / customer_idnajwyższanie zmienia się, nie jest surowym PII — domyślny wybór
E-mail / telefonwysokabywają zmieniane, są PII — hashuj (SHA-256) jako klucz
stape_user_idper urządzenietylko online ↔ online

E-mail i telefon też są dobrymi pomostami — zwłaszcza w omnichannel. Klasyczny przykład: zakup w sklepie stacjonarnym z kartą lojalnościową powiązaną z e-mailem; POS rozwiązuje kartę do tych danych i wstawia je do webhooka, a online ten sam zalogowany klient ma je w koncie. „Mniej stabilne” znaczy tu tylko „mutowalne i będące PII”, nie „nieużyteczne”. Warunek pozostaje jeden: identyczna wartość i ta sama postać (surowa albo hash) po obu stronach.

Konfiguracja — ścieżka online (zapis)

Krok 1 — zmienne odczytujące fbc/fbp z cookies

Dwie zmienne typu „Wartość pliku cookie”, nazwy cookies: _fbc i _fbp (z podkreślnikiem — to standardowe nazwy Facebooka). Czytam je z cookies, bo przeglądarka nie wysyła nagłówków o nazwie fbc/fbp — zmienna „Nagłówek żądania” zwróciłaby pusto.

Krok 2 — zmienna z identyfikatorem pomostowym

Zmienna typu „Nagłówek żądania”, nazwa x-stape-user-id. To jeden z dwóch identyfikatorów, po których reStore będzie dopasowywać profil.

Krok 3 — zmienna reStore (serce układu)

Zmienna typu Stape Store reStore:

  • Only restore data: odznaczone — zmienna ma też zapisywać fbc/fbp na page_view, nie tylko odczytywać.
  • List of identifiers (dwa wiersze): stape_user_id = zmienna z kroku 2, oraz user_id = {{user_id}}.
  • List of data to restore: fbc i fbp ze zmiennych z kroku 1.

reStore dopasowuje z fallbackiem — próbuje kolejno, aż któryś identyfikator trafi, a przy zapisie indeksuje profil po każdym niepustym. Listę „data to restore” warto trzymać minimalną (fbc, fbp) — bez dokładania tam samego identyfikatora.

Warto rozumieć różnicę między tym, co tu robię, a Writerem z poprzedniego use case:

AspektStape Store WriterStape Store reStore
Typ w GTMtag (akcja)zmienna (zwraca wartość)
Kierunektylko zapiszapis + odczyt
Tożsamośćjeden Document Keylista identyfikatorów (stitching)
Typowe użyciezapis pod jeden kluczresolucja tożsamości + enrichment

reStore to z grubsza Writer + Lookup + zszywanie tożsamości w jednym dwukierunkowym komponencie — wygoda specyficzna dla Stape Store. Na Firestore czy Supabase ten sam efekt złożysz ręcznie z osobnego zapisu, odczytu i logiki dopasowania; porównanie backendów i kosztów (Stape Store vs Firestore vs Supabase) opisuję przy pierwszym use case.

Krok 4 i 5 — wywołanie zapisu na page_view

Zmienna reStore działa dopiero, gdy coś ją odczyta. Dlatego dodaję przekształcenie „Uzupełnij zdarzenie” (parametr user_data = zmienna reStore, zakres ograniczony do taga Meta PageView) i sam tag Meta — PageView (Facebook CAPI, event PageView, trigger page_view). Online Meta i tak ma fbc/fbp z cookies — istotny jest efekt uboczny: wywołanie reStore utrwala je w magazynie na potrzeby przyszłego webhooka.

Konfiguracja — ścieżka offline (odczyt)

Krok 6 — wyciągnięcie pojedynczych pól

reStore zwraca obiekt, nie pojedynczą wartość. Dlatego dwie zmienne Object Property Extractor: Object = zmienna reStore, Property = fbc (dla jednej) i fbp (dla drugiej).

Krok 7 i 8 — wstrzyknięcie do zdarzenia Purchase

Przekształcenie „Uzupełnij zdarzenie”: fbc/fbp ze zmiennych z kroku 6, zakres ograniczony do taga Purchase. Dokładam tylko klucze, które tag FB CAPI faktycznie czyta — pól nierozpoznawanych nie wnoszą nic, więc nie zaśmiecam nimi zdarzenia.

Na końcu tag Meta — Purchase (Facebook CAPI, event Purchase, trigger na webhooku). Dostaje przywrócone fbc/fbp → wyższa jakość dopasowania konwersji offline.

Realny scenariusz: logowanie w połowie ścieżki

Najczęstsze pytanie: co, gdy użytkownik wchodzi anonimowo, przegląda produkty, a user_id pojawia się dopiero po zalogowaniu w połowie sesji? Czy most powstanie? Tak — pod warunkiem, że po zalogowaniu padnie co najmniej jeden page_view (lub inne zdarzenie wywołujące reStore).

Decyduje o tym jeden fakt: fbc/fbp żyją w cookies (_fbc/_fbp, ~90 dni), więc czytam je przy każdym żądaniu, nie tylko na pierwszym wejściu. Na pierwszym zalogowanym page_view mam więc naraz user_id (z logowania) i fbc/fbp (z cookies):

Etapstape_user_iduser_idfbc/fbpCo robi reStore
Anonim — karty produktu✅ X❌ pusty✅ cookieszapis profilu pod stape_user_id = X
Logowanie → page_view✅ X✅ U✅ cookiestrafia na profil po X, dokłada user_id = U
Webhook offline⚠️ serwera (miss)✅ U❌ brakdopasowuje po U → odtwarza fbc/fbp

Grają tu dwie role naraz: stape_user_id jest klejem sesji (stała wartość w tej samej przeglądarce, dzięki czemu reStore dopina user_id do profilu z fazy anonimowej), a fbc/fbp z cookies gwarantują, że nawet bez zszycia profil pod user_id powstanie od nowa z kompletem danych.

Gdzie to się sypie: logowanie bez kolejnego page_view (SPA, AJAX-login na tej samej stronie) — user_id nigdy nie spotka reStore. Dlatego lepiej nie polegać wyłącznie na page_view: najpewniejszy zapis profilu pod user_id to zdarzenie login albo online’owy checkout. Wystarczy jedno odpalenie reStore po identyfikacji.

Pułapki, na które uważam

  • Nazwa cookie z podkreślnikiem_fbc/_fbp, nie fbc/fbp. Bez podkreślnika zmienna zwróci pusto. _fbc bywa puste, gdy wejście nie miało fbclid; _fbp powinno być prawie zawsze.
  • reStore zwraca obiekt — bez Object Property Extractora nie wstrzykniesz pojedynczego fbc/fbp do zdarzenia.
  • Cicha awaria — przy rozjechanym identyfikatorze tag Meta odpala się poprawnie, a brak fbc/fbp zobaczysz dopiero po jakości dopasowania w Menedżerze Zdarzeń, nie w debuggerze.

Weryfikacja

  1. Ścieżka online: w podglądzie na page_view sprawdź, że reStore tworzy/aktualizuje dokument i że zawiera fbc/fbp.
  2. Ścieżka offline: zasymuluj webhook zakupu i potwierdź, że reStore zwraca fbc/fbp, a Object Property Extractor je wyciąga. Jeśli pusto — najpierw podejrzewaj niedopasowany identyfikator.
  3. Meta Test Events: sprawdź, że Purchase z webhooka dociera z fbc/fbp.

Kolejność pracy — najpierw potwierdź sam, potem zlecaj. Ten webhook symuluj lokalnie, zanim wyślesz cokolwiek deweloperowi. Konfiguracja po stronie sGTM (Data Client, reStore, transformacje) i kod webhooka w systemie offline (POS, bramka płatności) to dwie różne odpowiedzialności — a do kodu POS-a zwykle nie masz dostępu. Jeśli wyślesz specyfikację „na YOLO”, a coś po stronie sGTM nie zagra, debugujesz dwie warstwy naraz, w cudzym kodzie.

Lepiej wystaw kontrolowany test — choćby prosty przycisk wysyłający przykładowy POST na endpoint webhooka. Potwierdź, że reStore przywraca fbc/fbp, a Meta Test Events łapie konwersję. Dopiero gdy sam masz zielone, przekazujesz deweloperowi gotową specyfikację payloadu i ścieżki — wtedy jego zadanie redukuje się do odwzorowania znanego, działającego kontraktu w systemie POS, a nie zgadywania razem z Tobą.

Prywatność — zanim trafi na produkcję

fbc/fbp powiązane ze stabilnym identyfikatorem to dane służące do śledzenia — ich utrwalanie to inny próg niż przekazanie hitu:

  • Zapis przez reStore warunkuj zgodą marketingową.
  • Ustaw TTL na dokumentach — fbc/fbp mają i tak ograniczoną żywotność po stronie Meta.
  • Przekazywanie identyfikatora użytkownika w webhooku uwzględnij w polityce prywatności — to świadome łączenie tożsamości online z transakcją offline.

Powiązane wpisy