Meta Pixel i Conversions API
Pomiar konwersji reklamowych Meta (Facebook / Instagram): rejestracja konta oraz opłacona transakcja są raportowane jednocześnie z przeglądarki (Meta Pixel) i z serwera (Conversions API), z tym samym identyfikatorem zdarzenia — dzięki czemu Meta widzi jedną konwersję, a nie dwie.
Integracja jest włączana i konfigurowana z panelu administratora:
Panel administratora → Marketing — Meta (/dashboard/marketing-settings).
Domyślnie jest wyłączona; identyfikator zbioru danych 1222907778063897 jest
wpisany wstępnie, token dostępu trzeba wkleić.
👤 Instrukcja dla marketingu
Co jest mierzone
| Zdarzenie | Kiedy się wysyła | Wartość |
|---|---|---|
PageView | przy wejściu na stronę i przy każdej zmianie widoku w aplikacji | — |
CompleteRegistration | dopiero po utworzeniu konta potwierdzonym przez backend | — |
Purchase | dopiero po potwierdzeniu płatności przez Przelewy24 | rzeczywista kwota transakcji, PLN |
Czego nie zobaczysz w Menedżerze zdarzeń — i tak ma być:
- kliknięcie „Zarejestruj się", wysłanie formularza, wpisanie kodu SMS bez powodzenia — rejestracja nieudana nie jest konwersją,
- powrót ze strony płatności, dopóki operator nie potwierdzi przelewu,
- odświeżenie strony potwierdzenia płatności — jedna płatność to jedna konwersja,
- ruch z
dev.klient.acepark.pl,panel.acepark.pli ze środowisk lokalnych — do zbioru danych trafia wyłącznie produkcyjny portal klientaklient.acepark.pl, - ruch użytkownika, który nie zaakceptował banera cookies.
Włączenie integracji
- Menedżer zdarzeń Meta → Ustawienia zbioru danych → Wygeneruj token dostępu.
- Panel administratora → Marketing — Meta: wklej identyfikator zbioru danych i token, przełącz Integracja aktywna, zapisz.
- Token jest zapisywany po stronie serwera i nigdy nie wraca do przeglądarki — formularz pokazuje tylko cztery ostatnie znaki. Puste pole przy kolejnym zapisie zostawia zapisany token bez zmian; przycisk „Usuń zapisany token" go kasuje.
- Włączenie bez kompletu danych jest odrzucane — panel nie pozwoli twierdzić, że integracja działa, gdy nie miałaby czego wysłać.
- Testuj połączenie — przycisk w panelu wysyła pojedyncze
PageViewzapisanym tokenem i pokazuje odpowiedź Meta. Działa również przy wyłączonej integracji, więc dane dostępowe można sprawdzić przed jej włączeniem. Wymaga wypełnionego kodu zdarzeń testowych: bez niego ping trafiłby do zbioru produkcyjnego, więc akcja się na to nie zgadza.
Zgody
Pixel nie ładuje się w ogóle, dopóki odwiedzający nie zaakceptuje banera cookies. Ta sama odpowiedź steruje wysyłką serwerową: bez zgody nie powstaje żaden event — ani przeglądarkowy, ani z Conversions API.
Baner nie pojawia się w widoku kalendarza rezerwacji /book osadzanym na stronie
klubu w iframe — tam o zgodę pyta strona nadrzędna, a ruch z widgetu nie jest
raportowany.
Weryfikacja w Menedżerze zdarzeń
- Otwórz
https://klient.acepark.pl, zaakceptuj baner cookies. - W Events Managerze zakładka Testuj zdarzenia pokaże
PageView. - Rejestracja testowa → jedno zdarzenie
CompleteRegistrationopisane jako „Przeglądarka i serwer" (deduplikacja zadziałała). Dwa osobne wiersze oznaczają, że identyfikatory się rozjechały.
Aby serwer wysyłał do zakładki testowej zamiast do zbioru produkcyjnego, wpisz tymczasowo kod z tej zakładki w polu Kod zdarzeń testowych w panelu.
🔧 Dokumentacja techniczna
Przepływ
Moduły
| Plik | Rola |
|---|---|
lib/meta/config.ts | wersja Graph API i nazwy ciasteczek _fbp / _fbc |
lib/analytics/config.ts | lista hostów produkcyjnych i stałe zgody, wspólne dla Meta i Google Ads |
lib/meta/settings.ts | odczyt ustawień: konfiguracja publiczna (cache) i dane dostępowe CAPI |
lib/actions/meta-tracking-settings.ts | zapis ustawień z panelu (tylko ADMIN) |
lib/meta/hash.ts | normalizacja i SHA-256 dla em, ph, external_id |
lib/meta/events.ts | nazwy zdarzeń i budowa event_id wspólna dla obu stron |
lib/analytics/consent.ts | odczyt zgody i mirror do ciasteczka cookie-consent (wspólny z Google Ads) |
lib/meta/server-context.ts | _fbp, _fbc, IP, User-Agent, event_source_url z żądania |
lib/meta/capi.ts | wysyłka POST /v21.0/<pixel_id>/events |
lib/meta/registration.ts | CompleteRegistration po utworzeniu konta |
lib/meta/purchase.ts | zapis kontekstu przy inicjalizacji płatności i Purchase z webhooka |
lib/meta/pixel-client.ts | wysyłka zdarzeń przeglądarkowych + kolejka i blokada powtórek |
components/analytics/meta-pixel.tsx | kod bazowy Pixela i PageView przy zmianie trasy |
components/analytics/meta-purchase-tracker.tsx | Purchase na stronie potwierdzenia |
Dwie bramki
Zdarzenie powstaje tylko wtedy, gdy oba warunki są spełnione:
- Ustawienie —
meta_tracking_settings.enabled = 1z identyfikatorem zbioru danych i tokenem (panel administratora). Ustawienie jest per tenant. - Host —
isTrackingHost()przepuszcza wyłącznieklient.acepark.pl(lib/analytics/config.ts, wspólny z Google Ads).NODE_ENVsię do tego nie nadaje: worker deweloperski to również build produkcyjny. Środowisko deweloperskie korzysta z osobnej bazy D1, więc włączenie integracji na devie i tak nie dotyka produkcji — bramka hosta jest drugim zabezpieczeniem, nie jedynym.
Do zgody odwiedzającego dochodzi więc trzeci warunek, sprawdzany przy każdym
żądaniu (readMetaRequestContext).
Tabela meta_tracking_settings
Migracja 0230_create_meta_tracking_settings.sql, jeden wiersz na tenant:
enabled, pixel_id, access_token, test_event_code, updated_at,
updated_by.
Konfiguracja publiczna (enabled, pixel_id) jest czytana przy każdym renderze
strony, więc trzyma się w cache KV (meta-tracking, TTL 300 s, unieważniany przy
zapisie). Token nie trafia do cache — czyta się go z D1 dopiero w momencie
wysyłki zdarzenia, czyli kilkanaście razy dziennie.
Deduplikacja
| Zdarzenie | event_id | Źródło identyfikatora |
|---|---|---|
CompleteRegistration | registration_<userId> | id konta utworzonego przez backend, zwrócone do przeglądarki jako conversionEventId |
Purchase | purchase_<sessionId> | session_id transakcji (uuid), znany obu stronom |
Identyfikatory są deterministyczne, więc powtórka po stronie przeglądarki lub ponowione powiadomienie od operatora nie tworzą drugiej konwersji.
_fbp / _fbc w evencie serwerowym
Oba identyfikatory to ciasteczka pierwszej strony na domenie
klient.acepark.pl, więc każde żądanie do naszego workera niesie je samo z
siebie — front nie musi ich przekazywać. Wyjątkiem jest Purchase: webhook
przychodzi z serwerów Przelewy24, bez ciasteczek i z ich adresem IP. Dlatego
saveMetaEventContext() zapisuje _fbp, _fbc, IP i User-Agent płatnika do
tabeli meta_event_context w momencie inicjalizacji płatności — czyli wtedy, gdy
żądanie robi jeszcze przeglądarka płatnika.
_fbc nigdy nie jest generowane sztucznie: brak ciasteczka oznacza brak pola.
Tabela meta_event_context
Migracja 0229_create_meta_event_context.sql.
| Kolumna | Znaczenie |
|---|---|
session_id | session_id transakcji (klucz główny) |
fbp, fbc | identyfikatory przeglądarki płatnika |
client_ip, user_agent | dane dopasowania |
event_source_url | strona, z której ruszyła płatność |
purchase_sent_at | znacznik wysyłki — ustawiany warunkowym UPDATE … WHERE purchase_sent_at IS NULL, co gwarantuje jedną wysyłkę mimo ponowień webhooka |
Wiersz powstaje wyłącznie dla płatnika ze zgodą na produkcji — jego brak jest równoznaczny z „nie raportujemy tej transakcji".
Strona potwierdzenia płatności
MetaPurchaseTracker odpytuje /api/public/meta/purchase-event?sessionId=…
(co 2 s, maksymalnie 10 prób), bo płatnik wraca z Przelewy24 zwykle chwilę przed
webhookiem. Endpoint odpowiada completed dopiero wtedy, gdy transakcja jest
rozliczona i serwer wysłał już swoje zdarzenie (purchase_sent_at), więc
przeglądarka nigdy nie raportuje konwersji, której nie ma po stronie serwera.
Wysłane zdarzenie jest zapamiętywane w localStorage, co blokuje powtórkę przy
odświeżeniu strony.
Czego nie raportujemy
- Transakcji opłaconych w całości z portfela klubowego oraz kodem rabatowym na 100% — pieniądze nie przeszły przez operatora, a saldo portfela jest przyznawane przez klub. Zaraportowanie takiej płatności zawyżałoby przychód przypisany reklamom.
- Rejestracji, w których konto już istniało (powracający opiekun potwierdzający
numer telefonu) —
provisionAccountForDraft()zwracacreatedAccount: null.
Wdrożenie
Migracje 0229 (kontekst przeglądarki płatnika) i 0230 (ustawienia) — yarn db:update. Żadnych zmiennych środowiskowych ani sekretów workera: dane dostępowe
wpisuje administrator w panelu. Bez tokenu Pixel się nie ładuje, a zdarzenia
serwerowe są pomijane z ostrzeżeniem w logach (meta/capi).