Appearance
Facebook Catalog & Conversions API
Flow ID: IN-07 | Module(s): feeds, MetaConversionsApi, DeferredTask | Complexity: High Last Updated: 2026-05-07
Business Overview
This integration has two distinct components:
Facebook Catalog Feed -- An XML product feed for the Facebook/Meta Commerce Manager. The controller (
AdvFacebookCatalog) extendsAdvGoogleCatalog, inheriting 95% of the RSS 2.0 generation logic and overriding only the product ID transformation and security/config keys.Meta Conversions API (CAPI) -- Server-side event tracking that sends 7 actively dispatched event types (plus 1 handler-only type) to Meta's Graph API. Events are dispatched via the
DeferredTaskRunner(post-response execution) with circuit breaker protection, ensuring zero impact on TTFB. TheEventTypeenum defines 17 standard Meta event types; 8 havecreateCustomDatahandlers, 7 of which are wired to controllers. This provides server-side deduplication with the browser-based Meta Pixel.
API Reference
Catalog Feed Endpoint
| Method | URL | Auth | Response |
|---|---|---|---|
| GET | /xml/facebook-catalog | Token query param (optional) | RSS 2.0 XML |
Same query parameters as Google Merchant: token, brand, category, top, stock, curr.
Conversions API Events
All events are sent server-to-server to Meta's Graph API (graph.facebook.com/v{version}/{pixel_id}/events).
| Event | Trigger Location | Priority | Custom Data |
|---|---|---|---|
| AddToCart | AdvApiCartController | NORMAL (50) | content_ids, contents (id/qty/price), content_name, content_type, currency, value |
| Purchase | Adv_checkout | CRITICAL (100) | content_ids, contents, currency, delivery_category, order_id, value |
| InitiateCheckout | Adv_order | CRITICAL (100) | content_ids, contents, currency, num_items, value |
| Search | Adv_search | NORMAL (50) | search_string |
| ViewContent | Adv_products, Adv_vendors | NORMAL (50) | content_ids, content_name, content_type |
| CompleteRegistration | Adv_customer | NORMAL (50) | email, status |
| AddToWishlist | Adv_customer | NORMAL (50) | content_ids, content_name, content_type, contents, currency, value |
| Contact | Not wired (handler only) | -- |
Code Flow
Catalog Feed
Controller Inheritance
AdvXml -> AdvGoogleCatalog -> AdvFacebookCatalog -> Facebook_catalogAdvFacebookCatalog overrides four methods from AdvGoogleCatalog:
__construct(): UsesxmlDbIdKey['facebookCatalog']and readsXML_FEEDS.CUSTOM_PRICE_FACEBOOKsecureXml(): ChecksIS_ENABLED_FACEBOOK,IS_PROTECTED_FACEBOOK,FACEBOOK_TOKENparseItem(): Callsparent::parseItem()then overridesg:idwithproductIdToFaceBookId()(Facebook-specific prefix)getDbData(): Callsproduct_model->getFeedProductsForFacebook($brandId, $categoryId, $topSellers, $topStock)with optional query param filters (brand,category,top,stock)
Product ID Generation
php
// ecommercen/helpers/tracking_helper.php
function productIdToFaceBookId($productId) {
return registry->value('TRACKING', 'PRODUCT_ID_PREFIX_FACEBOOK') . $productId;
}Conversions API
Service Initialization
FacebookConversionService.__construct()
1. Check FACEBOOK_CONVERSION.ENABLED registry flag
2. In non-production: require PIXEL_TESTING_CODE (test mode)
3. Load PIXEL_ID + PIXEL_ACCESS_TOKEN
4. Initialize Meta Business SDK: Api::init(pixelId, null, accessToken, $log_crash=false)
(false disables the SDK CrashReporter which otherwise writes to php://stdout per request)
5. Configure custom HTTP client: MetaHttpClient (timeout, connectTimeout)
6. Create Config VO and MetaClientEvent Dispatch Flow
Controller (e.g., Adv_checkout)
-> Build $customData array (event-specific payload)
-> Build $userData array (customer PII, or null for anonymous)
-> di()->get('deferred_task')->defer(
task: fn() => $fb->dispatchEvent(EventType::PURCHASE, $customData, $userDataArr),
maxCostSeconds: $fb->getMaxCostSeconds(),
priority: DeferredTaskRunner::PRIORITY_CRITICAL,
name: 'meta.purchase',
)After fastcgi_finish_request() (response already sent to client):
DeferredTaskRunner->run()
-> flushResponse() # fastcgi_finish_request / litespeed_finish_request
-> Execute tasks by priority (CRITICAL=100 > NORMAL=50 > LOW=10)
-> FacebookConversionService->dispatchEvent()
-> circuitBreaker->isOpen() # fail fast if breaker tripped
-> createEvent() # build Event with UserData + CustomData
-> MetaClient->dispatch() # EventRequest->execute() to Graph API
-> circuitBreaker->recordSuccess() or recordFailure()User Data Collection
The service collects user data at two levels:
Anonymous users (no $userData passed):
external_id: Fromfb_session_usersession variableclient_ip_address: FromCI_Input->ip_address()client_user_agent: From constructor-injected user agent stringfbc: From_fbccookie (or generated fromfbclidquery param)fbp: From_fbpcookie (or server-generated viaFbpVO)
Authenticated users (full $userData array):
- All anonymous fields plus: email, phone, first/last name, city, state, country, zip, birthdate (parsed to dobd/dobm/doby), f5first/f5last (first 5 chars), fi (first initial)
fb_login_id: Set whenlogin_referrersession variable is'facebook'andoauth2_idsession variable is present (Facebook OAuth2 login)
Cookie Management
| Cookie | Format | TTL | Purpose |
|---|---|---|---|
_fbp | fb.{subdomain}.{timestamp_ms}.{random} | Set by browser pixel | Facebook browser parameter |
_fbc | fb.{subdomain}.{timestamp_ms}.{fbclid} | 90 days | Facebook click identifier |
If _fbc is absent but fbclid query param exists, the service generates and sets the _fbc cookie in fb.1.{timestamp_ms}.{fbclid} format.
If _fbp is absent, the server generates a synthetic value via the Fbp value object: fb.1.{timestamp_ms}.{random_int} (random between 1,000,000,000 and 1,999,999,999).
Architecture
File Map
Catalog Feed
| File | Purpose |
|---|---|
application/controllers/Facebook_catalog.php | Thin CI router wrapper |
ecommercen/feeds/controllers/AdvFacebookCatalog.php | Feed controller -- extends Google, overrides ID/security |
ecommercen/feeds/controllers/AdvGoogleCatalog.php | Parent feed controller (inherited) |
src/Feeds/Output/OutputGoogleMerchantXml.php | RSS 2.0 output (shared with Google) |
Conversions API
| File | Purpose |
|---|---|
src/MetaConversionsApi/Service/FacebookConversionService.php | Main service -- init, dispatch, event creation, user data |
src/MetaConversionsApi/Client/ClientInterface.php | Client contract (dispatch, dispatchSafe) |
src/MetaConversionsApi/Client/MetaClient.php | Graph API client -- EventRequest->execute() |
src/MetaConversionsApi/Client/MetaHttpClient.php | Custom cURL HTTP client with configurable timeouts |
src/MetaConversionsApi/Enum/EventType.php | String-backed enum: 17 Meta event types |
src/MetaConversionsApi/Enum/ActionSource.php | String-backed enum: website, email, phone_call, etc. |
src/MetaConversionsApi/Pixel/Config.php | Config VO (pixelId, accessToken, testingCode) |
src/MetaConversionsApi/Pixel/Pixel.php | Pixel VO (id, nullable accessToken) |
src/MetaConversionsApi/ValueObject/Fb.php | Abstract base for fbp/fbc value objects |
src/MetaConversionsApi/ValueObject/Fbp.php | _fbp cookie format (fb.subdomain.time.random) |
src/MetaConversionsApi/ValueObject/Fbc.php | _fbc cookie format (fb.subdomain.time.clickId) |
Deferred Task Infrastructure
| File | Purpose |
|---|---|
src/DeferredTask/DeferredTaskRunner.php | Priority queue runner with time budget, post-response execution |
src/DeferredTask/DeferredTask.php | Task VO (callback, maxCostSeconds, priority, name) |
application/config/container/deferred_task.php | DI registration with configurable budget/logger |
application/config/container/circuit_breakers.php | DI registration for circuit_breaker.meta (and 4 other named breakers) |
application/config/circuit_breakers.php | Threshold, cooldown, multiplier config per circuit breaker |
Event Dispatch Locations
| Controller | Event | Deferred Task Name |
|---|---|---|
ecommercen/api/controllers/AdvApiCartController.php | AddToCart | meta.add_to_cart |
ecommercen/checkout/controllers/Adv_checkout.php | Purchase | meta.purchase |
ecommercen/eshop/controllers/Adv_order.php | InitiateCheckout | meta.initiate_checkout |
ecommercen/search/controllers/Adv_search.php | Search | meta.search |
ecommercen/eshop/controllers/Adv_products.php | ViewContent | meta.view_content |
ecommercen/eshop/controllers/Adv_vendors.php | ViewContent | meta.view_content |
ecommercen/eshop/controllers/Adv_customer.php | CompleteRegistration | meta.complete_registration |
ecommercen/eshop/controllers/Adv_customer.php | AddToWishlist | meta.add_to_wishlist |
Circuit Breaker Integration
The FacebookConversionService receives an optional CircuitBreaker instance. The Meta circuit breaker is registered as circuit_breaker.meta in application/config/container/circuit_breakers.php alongside four other named breakers (matomo, manago, project_agora, advisable_ai). Each breaker's threshold, cooldown, max cooldown, multiplier, and TTL buffer are loaded from application/config/circuit_breakers.php config keys (e.g., cb_meta_threshold, cb_meta_cooldown_seconds). The breaker uses the L2 cache adapter (cache.l2) for state persistence.
When Meta's API becomes unresponsive:
- Failures accumulate against the threshold (configurable, default 3)
- Circuit opens -- all subsequent
dispatchEvent()calls return immediately (isOpen()check) - After cooldown, a single probe request is allowed through (half-open state); the probe slot is claimed atomically via
storeIfAbsentto prevent concurrent probes - On probe success, circuit closes and all state is deleted; on failure, reopens with exponentially increased cooldown (multiplier, capped at
maxCooldownSeconds)
This prevents cascading timeouts when Meta's infrastructure is degraded, keeping the deferred task budget available for other tasks.
DeferredTaskRunner Mechanics
- Priority queue: Tasks execute in priority order (CRITICAL=100 first)
- Time budget: Configurable via DI container (default 10s); tasks declare
maxCostSeconds - Post-response: Runs after
fastcgi_finish_request()-- invisible to end users - Budget enforcement: Tasks exceeding remaining budget are skipped (logged as notice)
- Error isolation: Failed tasks do not block subsequent tasks
Data Model
Custom Data per Event Type
| Event | Fields | Notes |
|---|---|---|
| AddToCart | content_ids, contents[], content_name, content_type, currency, value | contents has id/qty/item_price per SKU |
| Purchase | content_ids, contents[], currency, delivery_category, order_id, value | delivery_category: in_store or home_delivery |
| InitiateCheckout | content_ids, contents[], currency, num_items, value | Aggregated cart data |
| Search | search_string | Search query text |
| ViewContent | content_ids, content_name, content_type | Product page identifiers |
| CompleteRegistration | email, status | Registration outcome |
| AddToWishlist | content_ids, content_name, content_type, contents[], currency, value | Same structure as AddToCart |
| Contact | Handler defined but not wired to any controller; available for client extension |
Event Payload Structure
Each event sent to Meta includes:
json
{
"event_name": "Purchase",
"event_time": 1712188800,
"event_source_url": "https://example.com",
"event_id": "{random_32_hex}",
"action_source": "website",
"opt_out": false,
"data_processing_options": [],
"custom_data": { ... },
"user_data": {
"em": "{sha256_email}",
"ph": "{sha256_phone}",
"fn": "{sha256_first_name}",
"ln": "{sha256_last_name}",
"ct": "{sha256_city}",
"st": "{sha256_state}",
"zp": "{sha256_zip}",
"country": "{sha256_country}",
"external_id": "{session_id}",
"client_ip_address": "{ip}",
"client_user_agent": "{ua}",
"fbc": "fb.1.{ts}.{clickid}",
"fbp": "fb.1.{ts}.{random}"
}
}Note: The Meta Business SDK handles PII hashing (SHA-256) automatically via the UserData setters.
Configuration
Registry Keys (FACEBOOK_CONVERSION Group)
| Key | Type | Purpose |
|---|---|---|
ENABLED | bool | Master toggle for Conversions API |
PIXEL_ID | string | Meta Pixel ID |
PIXEL_ACCESS_TOKEN | string | Server-side access token |
PIXEL_TESTING_CODE | string | Test event code (required in non-production) |
TIMEOUT | int | HTTP request timeout in seconds (default: 5) |
CONNECT_TIMEOUT | int | HTTP connection timeout in seconds (default: 3) |
Registry Keys (XML_FEEDS Group)
| Key | Type | Purpose |
|---|---|---|
IS_ENABLED_FACEBOOK | bool | Feed toggle |
IS_PROTECTED_FACEBOOK | bool | Enable token auth |
FACEBOOK_TOKEN | string | Expected token value |
CUSTOM_PRICE_FACEBOOK | bool | Enable custom price modifier |
Registry Keys (TRACKING Group)
| Key | Type | Purpose |
|---|---|---|
PRODUCT_ID_PREFIX_FACEBOOK | string | Prefix for Facebook product IDs |
DI Container
The deferred_task service is registered in application/config/container/deferred_task.php:
$timeBudgetSeconds: Fromdeferred_task_budget_secondsconfig$enabled: Fromdeferred_task_enabledconfig$logger:service(\Advisable\Logger\NamedLoggerInterface::class)— the same single Monolog logger every component shares (application/config/container/deferred_task.php:20). The runner calls$logger?->withName('deferred-task')so its records carry%channel% = deferred-task.
The FacebookConversionService itself is not registered explicitly — it is instantiated on demand by Adv_front_controller::__construct at ecommercen/core/Adv_front_controller.php:116-123. Its NamedLoggerInterface $logger (src/MetaConversionsApi/Service/FacebookConversionService.php:47) is fetched via di()->get(\Advisable\Logger\NamedLoggerInterface::class)->withName('facebook-conversion') at the call site, so service-internal log records carry %channel% = facebook-conversion. The MetaClient constructed inside the service uses its own channel meta-conversions-api (src/MetaConversionsApi/Client/MetaClient.php:28).
Environment Requirements
- Non-production environments require
PIXEL_TESTING_CODEto be set (events sent with test event code for debugging in Meta Events Manager) - Production environments can leave
PIXEL_TESTING_CODEempty
Client Extension Points
Catalog Feed
- Override controller: Subclass
AdvFacebookCataloginapplication/controllers/to customize field mapping - Custom price modifier: Enable
XML_FEEDS.CUSTOM_PRICE_FACEBOOKfor per-product markup - Product ID prefix: Configure
TRACKING.PRODUCT_ID_PREFIX_FACEBOOKfor catalog-pixel ID alignment
Conversions API
- Custom HTTP client: Implement
ClientInterfaceand inject via DI to replaceMetaClient - Additional events: The
EventTypeenum defines 17 event types; only 7 are actively dispatched (Contact has a handler but is not wired) -- additional types can be wired from custom controllers by callingdispatchEvent()with the appropriateEventType - Circuit breaker tuning: Adjust threshold, cooldown, and backoff parameters via DI container configuration
- Timeout tuning: Configure
TIMEOUTandCONNECT_TIMEOUTregistry keys per-client based on network conditions
Business Rules
Catalog Feed
- Feed disabled: Returns 403 when
IS_ENABLED_FACEBOOKis falsy - ID format: Uses Facebook-specific prefix (may differ from Google prefix) for cross-platform ID consistency
- Inherited behavior: All Google Merchant field mapping, sale price logic, and image handling apply identically
Conversions API
- Environment gate: In non-production, service is disabled unless
PIXEL_TESTING_CODEis set - Missing config: If
PIXEL_IDorPIXEL_ACCESS_TOKENis empty, all dispatches silently return - Circuit breaker: When open, all events are dropped silently (no logging, no error)
- Priority ordering: Purchase and InitiateCheckout events use
PRIORITY_CRITICAL(100) to ensure they execute first within the budget - Time budget: Each Meta task declares
maxCostSeconds = timeout + connectTimeout(default 8s). If the deferred task runner's budget is exhausted, remaining Meta events are skipped (logged at notice level) - Event deduplication: Each event gets a unique
event_id(32-byte random hex) for browser-server deduplication - Cookie fallback: Server generates synthetic
_fbp/_fbcvalues when cookies are absent, ensuring Meta can still attribute events - Error isolation: A failed dispatch is caught, logged, and recorded as a circuit breaker failure -- subsequent events still execute
Known Issues & Security Gaps
No known issues at this time.
Related Flows
- IN-06 Google Merchant -- Parent feed class inherited by the catalog component
- IN-01 Feed Generation -- Base feed framework
- IN-12 Analytics -- Meta Conversions API shares the Facebook integration surface
- SY-26 Circuit Breaker --
circuit_breaker.metaprotects CAPI dispatchers; events are silently dropped when the breaker is open - SY-27 Deferred Task Runner -- all 7 CAPI event types are dispatched post-response via the deferred task runner with
PRIORITY_CRITICAL
Wiki Guide: Circuit Breaker Guide -- circuit breaker pattern used by CAPI dispatchers