Skip to content

Developer guide

Add-on ID kv_meta_conversions_api
PHP namespace Tygh\Addons\KvMetaConversionsApi (app/addons/kv_meta_conversions_api/Tygh/Addons/KvMetaConversionsApi/)
Function prefix fn_kv_meta_conversions_api_
Language variables kv_meta_conversions_api.*
Tables ?:kv_meta_capi_events, ?:kv_meta_capi_orders, ?:kv_meta_capi_vendors

The add-on implements the Kavio Labs consent contract, which the Kavio Labs Cookie Consent add-on (kv_cookie_consent_mode) provides. Any other banner can use it too.

A consent banner exposes window.kvConsent (category names: necessary, preferences, analytics, marketing):

window.kvConsent = {
has: function (category) { /* true | false */ }, // alias: get()
hasDecision: function () { /* true when the visitor made a valid choice */ },
on: function (event, callback) { /* 'ready' | 'change'; returns an unsubscribe function */ }
};

and dispatches DOM events on document with the current state in event.detail:

document.dispatchEvent(new CustomEvent('kv:consent-ready', {detail: {necessary: true, preferences: false, analytics: true, marketing: true, decided: true}}));
document.dispatchEvent(new CustomEvent('kv:consent-changed', {detail: {/* same shape */}}));

kv_consent_changed is accepted as a legacy alias. This add-on reads marketing.

Rules:

  • If window.kvConsent exists when the page loads, it is the only source of the decision. While hasDecision() is false (first visit, expired choice, new policy version), the pixel does not load and nothing is sent, whatever the add-on’s own kv_meta_consent cookie says. The add-on subscribes with kvConsent.on('ready' | 'change').
  • Without window.kvConsent, the add-on listens for the DOM events, and for the CS-Cart cookie dialog (GDPR add-on, Klaro service kv_meta_pixel) when that integration is on.
  • The consent request to the server is sent only after the page scripts are ready, so it is safe to call kvConsent callbacks early in <head>.
kvMetaCapi.consent(true); // grant marketing consent (custom banners)
kvMetaCapi.consent(false); // deny or withdraw
kvMetaCapi.state(); // 'granted' | 'denied' | 'unknown'
kvMetaCapi.sent; // events sent on this page: [{pixel, name, id, data}]
kvMetaCapi.send({name: 'Contact', id: 'my-unique-id', data: {}, main: true, vendors: []}); // browser only

The add-on triggers $.ceEvent('trigger', 'kv_meta_capi.consent', [granted]) when the state changes.

Server-side events need the decision during the storefront request. The add-on asks, in this order:

  1. The PHP hook kv_meta_conversions_api_get_consent (see below). Any add-on can decide here.
  2. fn_kv_cookie_consent_mode_get_consent(), which returns the visitor’s valid choice or null (Kavio Labs Cookie Consent). If the function exists it is authoritative: null means “no decision yet”.
  3. A generic fn_kv_consent_has(string $category): ?bool, if a consent add-on defines it (null = unknown).
  4. Only when no consent add-on is present: the kv_meta_consent cookie (g / d) that the storefront script writes.

When Kavio Labs Cookie Consent is active, the add-on registers the service kv_meta_conversions_api (category marketing) through the kv_cookie_consent_mode_get_services hook. It lists the server events in the preference center, and the consent add-on deletes _fbp, _fbc and kv_meta_consent when marketing consent is withdrawn. No script is blocked, because the pixel loader waits for consent itself. The configuration script (window.kvMetaCapiConfig) is skipped by the consent add-on’s script blocker.

/**
* Decide marketing consent for the current visitor.
*
* @param string $category "marketing"
* @param bool|null $granted set true/false; leave null for the default resolution
*/
fn_set_hook('kv_meta_conversions_api_get_consent', $category, $granted);
/**
* Change or skip a catalog feed item.
*
* @param array $product Product (fn_get_products + fn_gather_additional_products_data)
* @param array|null $item Feed item (id, title, description, availability, condition, price, sale_price,
* link, image_link, additional_image_link, brand, item_group_id, product_type, mpn);
* set to null to leave the product out
*/
fn_set_hook('kv_meta_conversions_api_feed_item', $product, $item);

Example: send a Google product category with every feed item.

init.php
fn_register_hooks('kv_meta_conversions_api_feed_item');
// func.php
function fn_my_addon_kv_meta_conversions_api_feed_item($product, &$item)
{
if (is_array($item)) {
$item['product_type'] = 'Apparel & Accessories > Clothing';
}
}
Hook Purpose
before_dispatch Cart/wishlist snapshot, fbclid capture
redirect AddToCart / AddToWishlist before redirects (delivered with the next response)
dispatch_before_display Page events, AJAX payload (kv_meta_capi key), _fbp/_fbc after consent
place_order Browser context of the order (fbp, fbc, IP, user agent, page, consent); AddPaymentInfo
change_order_status_post Server-side Purchase when the order reaches a purchase status
update_profile CompleteRegistration
newsletters_update_subscriptions_post Lead (newsletter)
create_call_request_post Lead (call request)
update_company, delete_company Vendor pixel fields
kv_cookie_consent_mode_get_services Service registration in the Kavio Labs consent add-on
before_dispatch (admin area and console only; static handler License\Client::onBeforeDispatch) Runs the due license check and shows license notices
addons:tabs_content (pre, admin template) License card at the top of the add-on page
addons:manage (post, admin template) License badge, moved into the add-on’s row (4.17–4.21 have no per-row hook)
index:styles (post, admin template) license.less (kv-license block)

Templates: index:head_scripts (configuration and fbq stub), index:scripts (runtime script and cookie dialog translations), companies:detailed_content (vendor pixel block). Schemas: gdpr/klaro_config.post.php, menu/menu.post.php, permissions/admin.post.php, permissions/vendor_multivendor.post.php, settings/variants.functions.post.php.

Dispatch Area Purpose
kv_meta_conversions_api.consent (POST) storefront Stores the consent decision, writes _fbp/_fbc, releases or drops held server events
kv_meta_conversions_api.feed?key=…&format=csv|xml[&vendor_id=N] storefront Catalog feed. Wrong key: HTTP 403
kv_meta_conversions_api.process?token=… storefront Background queue worker (hourly HMAC token)
kv_meta_conversions_api.process admin, console Sends the queue (php admin.php --dispatch=kv_meta_conversions_api.process)
kv_meta_conversions_api.manage admin Event log
kv_meta_conversions_api.retry, .clear, .test (POST) admin Log actions and the test event
kv_meta_conversions_api.save_vendor_pixel (POST) admin, vendor panel Saves the vendor pixel block only
kv_meta_conversions_api_license.check (POST, console) admin, console License check (“Check now”, cron); generated

?:kv_meta_capi_events is the queue and the event log: event_id, event_name, pixel_id, vendor_id (0 = store pixel), company_id/storefront_id (settings context), order_id, payload (the API event JSON, without the token), status (Q, P, R, S, F, X), attempts, next_attempt_at, claim (worker lock), created_at, sent_at, http_status, response and test_event_code.

?:kv_meta_capi_orders stores one row per storefront order: fbp, fbc, client_ip, user_agent, source_url, external_id, consent (G/D/empty), placed_at, rendered_at (browser Purchase printed), queued_at (store pixel Purchase queued) and vendor_queued_at.

?:kv_meta_capi_vendors holds company_id, pixel_id, access_token and test_event_code.

Class Responsibility
Config Typed settings, per storefront; Graph API URL and version
Normalizer Meta normalisation and SHA-256 hashing (no CS-Cart dependencies, unit-tested)
UserData Customer data from the profile, checkout or order; IP, user agent, fbp/fbc cookies
ContentBuilder content_ids, contents, value, currency from product lines
Tracker Event creation (browser + server), session queues, cart diff, page events
OrderTracker Order context, browser and server Purchase, Multi-Vendor split
Queue Queue, async delivery, retry/backoff, batching, purge
GraphApi POST /{version}/{pixel}/events, response handling, log masking
Consent Consent mode and the decision resolution described above
CatalogFeed CSV / XML feed
VendorPixels Vendor pixel storage
ConflictDetector Other Meta pixel add-ons
License\Client License check client (generated from the shared Kavio Labs template); admin templates license/{info,card,status_bar,badge}.tpl, license.less

Tygh\Addons\KvMetaConversionsApi\License\Client verifies the license against the Kavio Labs license server. The client, the “Check now”/cron controller controllers/backend/kv_meta_conversions_api_license.php and the License tab template design/backend/templates/addons/kv_meta_conversions_api/license/info.tpl are generated from the shared Kavio Labs template; don’t edit them by hand.

  • Admin license UI (generated, REVISION 3; don’t edit by hand): design/backend/templates/addons/kv_meta_conversions_api/license/card.tpl (License card at the top of the add-on page, every tab; hooks/addons/tabs_content.pre.tpl), license/badge.tpl (badge in Add-ons > Manage add-ons; hooks/addons/manage.post.tpl), license/status_bar.tpl (chip/banner for the add-on’s own admin pages), design/backend/css/addons/kv_meta_conversions_api/license.less (loaded by the kv-license block of hooks/index/styles.post.tpl). The status bar is the first line inside the mainbox capture of views/kv_meta_conversions_api/manage.tpl (event log). All of them render nothing for vendors and for administrators without the “Manage settings” privilege. The “what is switched off” sentence is the language variable kv_meta_conversions_api.license_off_effect. License\Client assigns the view data to $kv_lic_kv_meta_conversions_api in its before_dispatch handler (only for administrators with the Manage settings privilege); the templates only read this variable, so they render nothing (and no error) while an older version of the PHP code is loaded, for example in the request that uploads a new zip over the installed add-on. Templates never call add-on functions; PHP code can call fn_kv_meta_conversions_api_license_ui($context).
  • fn_kv_meta_conversions_api_is_licensed($storefront_id = 0) (func.php) is the gate. It reads the stored state only and never contacts the server. With a storefront ID in the admin area it checks that storefront’s host.
  • Tracker::isTrackingActive() returns false: no kv_meta_capi_config_json (no pixel), no browser or server events, no order context.
  • Frontend controller: consent and relay answer 204 without recording anything, feed answers 404.
  • OrderTracker::onStatusChange() queues no Purchase for an order of an unlicensed storefront; Queue::process() sends nothing; the admin Send a test event (.test) refuses with a notice.
  • Client::boot() (init.php) registers a before_dispatch handler for the admin area and the console only. On admin page views and console requests it runs the check when it is due (key changed, or 24 hours after the last check; 1 hour after a failure) and shows the admin notice once per session.
  • Request: POST https://cs-cart.kaviolabs.com/index.php?dispatch=kv_licenses.check with JSON {"v":1, "license", "addon", "version", "hosts":[…], "platform":{"version", "edition"}, "nonce"}, sent through Tygh\Http (3 s connect, 5 s total; kept out of the CS-Cart request log). The answer is accepted only when its Ed25519 signature matches a public key embedded in the client and it echoes the nonce.
  • State: storage key kv_license_kv_meta_conversions_api (?:storage_data), or var/kv_storage/kv_meta_conversions_api/license.json while the full page cache is active, protected by an HMAC with the store’s crypt key.
  • Cron: php admin.php --dispatch=kv_meta_conversions_api_license.check.
  • Development stores only (http_host and https_host are development hosts): the constants KV_LICENSE_SERVER_URL, KV_LICENSE_TEST_HOST (act as if the development storefronts ran on that host) and KV_LICENSE_TEST_TIME_OFFSET (seconds). They are ignored on production hosts.

Storefront requests only insert queue rows. In automatic mode, a shutdown function then either flushes the response with fastcgi_finish_request() / litespeed_finish_request() and sends up to 100 events, or opens a non-blocking connection to kv_meta_conversions_api.process. That request is fired and forgotten, at most once every 2 seconds. Workers claim rows with UPDATE … SET claim = … LIMIT n, so parallel workers never send an event twice. Events are grouped per pixel, token and test code, and sent in batches of up to 500. The access token travels in the JSON body, never in the URL, and is masked in CS-Cart’s HTTP request log.

tests/mock/router.php is a mock of the Conversions API endpoint. It validates the payload (hash format, required fields, time window) and records every request. Run it with PHP’s built-in server and point the add-on at it in development only:

// local_conf.php or config.local.php of a development store
define('KV_META_CONVERSIONS_API_GRAPH_URL', 'http://127.0.0.1:8197');

The settings page shows a warning while the constant is defined. Unit tests for normalisation and hashing: php tests/unit/run.php.

Tygh/Addons/KvMetaConversionsApi/License/Ed25519.php (checks the signature of the license server’s answers on servers whose PHP has no sodium extension) is derived from paragonie/sodium_compat, ISC License, Copyright (c) 2016-2023 Paragon Initiative Enterprises and Copyright (c) 2013-2019 Frank Denis. The full license notice is in the file header.