Skip to content

Tracking Internals

Dual-channel architecture

Every tracking event is fired through two channels simultaneously:

  1. 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.
  2. 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 with fbq().

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()
  • _fbp cookie — Facebook browser ID, persisted across sessions
  • _fbc cookie — 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:

  1. hookActionFrontControllerInitAfter — if _fbp is seen associated with a different id_guest, pending events for the stale guest are remapped to the current guest.
  2. 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