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.
fbc/fbp z cookies
RECORD
fbc/fbp pod user_id
bez fbc/fbp
RESTORE
CAPI, z fbc/fbp
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.
| Identyfikator | Co spina | Ograniczenie |
|---|---|---|
stape_user_id | online ↔ online (ta sama przeglądarka) | per żądanie → nie przechodzi do webhooka z serwera |
user_id | online ↔ 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:
| Identyfikator | Stabilność | Uwagi |
|---|---|---|
Wewnętrzne user_id / customer_id | najwyższa | nie zmienia się, nie jest surowym PII — domyślny wybór |
| E-mail / telefon | wysoka | bywają zmieniane, są PII — hashuj (SHA-256) jako klucz |
stape_user_id | per urządzenie | tylko 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/fbpnapage_view, nie tylko odczytywać. - List of identifiers (dwa wiersze):
stape_user_id= zmienna z kroku 2, orazuser_id={{user_id}}. - List of data to restore:
fbcifbpze 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:
| Aspekt | Stape Store Writer | Stape Store reStore |
|---|---|---|
| Typ w GTM | tag (akcja) | zmienna (zwraca wartość) |
| Kierunek | tylko zapis | zapis + odczyt |
| Tożsamość | jeden Document Key | lista identyfikatorów (stitching) |
| Typowe użycie | zapis pod jeden klucz | resolucja 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):
| Etap | stape_user_id | user_id | fbc/fbp | Co robi reStore |
|---|---|---|---|---|
| Anonim — karty produktu | ✅ X | ❌ pusty | ✅ cookies | zapis profilu pod stape_user_id = X |
Logowanie → page_view | ✅ X | ✅ U | ✅ cookies | trafia na profil po X, dokłada user_id = U |
| Webhook offline | ⚠️ serwera (miss) | ✅ U | ❌ brak | dopasowuje 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, niefbc/fbp. Bez podkreślnika zmienna zwróci pusto._fbcbywa puste, gdy wejście nie miałofbclid;_fbppowinno być prawie zawsze. - reStore zwraca obiekt — bez Object Property Extractora nie wstrzykniesz pojedynczego
fbc/fbpdo zdarzenia. - Cicha awaria — przy rozjechanym identyfikatorze tag Meta odpala się poprawnie, a brak
fbc/fbpzobaczysz dopiero po jakości dopasowania w Menedżerze Zdarzeń, nie w debuggerze.
Weryfikacja
- Ścieżka online: w podglądzie na
page_viewsprawdź, że reStore tworzy/aktualizuje dokument i że zawierafbc/fbp. - Ś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. - 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/fbpmają 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.