Tracking Internals¶
Dual-channel architecture¶
Every tracking event is fired through two channels simultaneously:
- Conversions API (CA) — PHP/server-side, sent directly to Facebook's Graph API via the Facebook Business SDK. Fires inside PS hooks, immediately on the triggering HTTP request.
- Facebook Pixel (JS) — browser-side
fbq()calls, injected via a Vite-compiled frontend script. The script polls the server for queued events and fires them withfbq().
Both channels carry the same event_id so Facebook automatically deduplicates them.
Event flow¶
1. PS hook fires (e.g. actionCartUpdateQuantityBefore)
└── CAEventBuilder::addToCart() builds the event
├── Sets event_name, event_time, event_id (UUID), event_source_url, user_data, custom_data
└── CAEventBuilder::sendEvent()
├── Sends event to Facebook Graph API (CA channel)
└── Persists FbpEvent row to fbp_event table
├── ca_sent = 1
├── js_sent = 0
└── js_event = JSON payload for browser-side fbq() call
2. Browser loads page, frontend JS starts
└── Polls ConsumerController (?action=poll)
└── Returns: { pixel: { pixel_id, external_id }, events: [ { id, event_type, js_event } ] }
└── For each event:
├── fbq('track', event_type, js_event_data) ← fires the pixel
└── Calls ConsumerController (?action=confirm&id_fbp_event=N)
└── Marks fbp_event.js_sent = 1
Guest context¶
On every front controller init (actionFrontControllerInitAfter), the module captures:
- User agent —
$_SERVER['HTTP_USER_AGENT'] - Client IP — via
Tools::getRemoteAddr() _fbpcookie — Facebook browser ID, persisted across sessions_fbccookie — Facebook click ID, set when a user arrives via a Facebook ad
These values are stored in fbp_guest_context keyed by id_guest and attached to every CA event's user_data. If the user is logged in, name, email, and date of birth are also hashed and included.
Guest ID remapping¶
PrestaShop can renew id_guest during checkout (e.g. at order creation). When this happens, events queued under the old guest ID would never be polled by the browser (which now has the new id_guest).
The module handles this in two places:
hookActionFrontControllerInitAfter— if_fbpis seen associated with a differentid_guest, pending events for the stale guest are remapped to the current guest.ConsumerController::remapStaleGuestEvents()— a safety net: runs the same remap logic before the poll query, catching cases where the renewal happened after the hook fired.
Product ID formatting¶
The product ID used in tracking events follows the pixel setting's ID format (cs_id_format) and use reference (cs_use_reference_id) flags:
- Default:
{id}→"42"(product ID) or"42-7"(product ID + combination ID when combinations are exported) - Custom template: e.g.
ps_{id}→"ps_42" - Reference mode:
{ref}→ product's reference field value
This must match what the catalog export uses, so tracking events reference the same IDs as the feed.
Deduplication¶
Each event is assigned a unique event_id (generated by Utilities::createEventId()). The same ID is used for both the CA event and the JS pixel event stored in fbp_event.event_id. Facebook uses this to deduplicate.
fbp_event table¶
| Column | Description |
|---|---|
id_fbp_event |
Auto-increment primary key |
id_guest |
PS guest ID this event belongs to |
event_type |
Facebook event name (e.g. AddToCart) |
ca_event |
Raw CA event JSON (currently stored as {}) |
js_event |
JSON payload for the browser-side fbq() call |
ca_sent |
1 when the CA channel has sent this event |
js_sent |
1 when the browser has confirmed firing fbq() |
event_id |
Shared deduplication ID |
external_id |
Hashed guest external ID |
creation_date |
When the event was created |
update_date |
Last update timestamp |