Skip to content

Tracking pipeline ​

Two halves: the browser side, where the theme renders one GTM snippet and a Consent Mode default and pushes events to the dataLayer, and the server side, where Evolve records conversions and delivers them to the GA4 Measurement Protocol and the Meta Conversions API. The webmaster view is on Tracking and consent.

The two halves ​

Emission ​

Evolve::emitsTracking() (core/src/Evolve.php) decides whether anything leaves the site: evolve.tracking.emit (TRACKING_EMIT) when set, otherwise EnvironmentType::emitsTracking(), which is true for live only. The theme partials and TrackingManager::record() both consult it.

Browser side ​

theme/views/head.blade.php renders, in order, the verification metas, preconnects (when emitting), the include of theme::consentMode, the reCAPTCHA script when a site key exists, and the GTM snippet when emitting and services.google.gtm-id is set. theme/views/consentMode.blade.php defines dataLayer and gtag(), reads the evolve_consent cookie, exposes window.EVOLVE_CONSENT and window.evolveConsentAllows(category), and calls gtag('consent', 'default', ...) with analytics_storage, ad_storage, ad_user_data, ad_personalization granted or denied per the policy, plus wait_for_update: 500 for opt-in. The same view defines window.evolveConsentUpdate({analytics, marketing}), the bridge for consent decided outside the banner (#300): it writes evolve_consent in the banner's format, sets EVOLVE_CONSENT.state, sends gtag('consent', 'update') only when managedBy is evolve, pushes consent_updated and dispatches evolve:consent-updated; unchanged choices are skipped. With evolve.tracking.consent.cmp = cookiebot it also listens to CookiebotOnConsentReady, CookiebotOnAccept and CookiebotOnDecline. theme/views/consentPlatform.blade.php, included last in head.blade.php, renders Cookiebot's uc.js (synchronous, data-blockingmode="auto") when the driver and the ID are set. theme/views/trackingConfig.blade.php renders the deprecated window.*_TRACKING globals behind TRACKING_LEGACY_VARIABLES and the Hotjar snippet, both only when emitting.

In the starter theme, theme/theme/src/lib/cookie-consent.ts holds the Alpine store consent (analytics, marketing, acceptAll(), rejectAll(), save()), writes the cookie for cookieLifetimeDays, calls gtag('consent', 'update', ...) and pushes consent_analytics and consent_marketing to the dataLayer. theme/theme/src/lib/event-tracking.ts is the event layer: genericTrack(event, payload) pushes to the dataLayer, window.gtmTrack aliases it, helpers build generate_lead, form_submit, purchase and the funnel events, and [x-track] attributes are wired on DOM ready. theme/gtm_container.json is the matching container export.

Server side ​

core/src/Tracking/:

ClassRole
TrackingManagerrecord(TrackingEvent): snapshots the request (IP, user agent, _ga, _fbp, _fbc cookies, consent) into a UserContext and dispatches one Jobs\SendServerSideTrackingEvent per enabled driver on evolve.tracking.queue
TrackingEventname, event ID, params, user context, timestamp; factory generateLead(Form, Request, eventId)
UserContext, ConsentState, Consent, ConsentPolicy, ConsentCategorythe consent snapshot taken at record time; ConsentPolicy::OptOut grants by default, OptIn denies
Drivers\GoogleAnalyticsMeasurementProtocolDriverenabled when measurement ID and ANALYTICS_API_SECRET exist; posts to the collect endpoint, or to the debug endpoint with TRACKING_DEBUG
Drivers\MetaConversionsApiDriverenabled when pixel ID and FACEBOOK_CONVERSIONS_API_KEY exist; adds FACEBOOK_TEST_EVENT_CODE when set
Support\HashesPersonalDataSHA-256 of normalised email and digits-only phone before they leave
Support\EventIdthe shared ID that lets the browser and server copy of one event deduplicate

Callers: Http\Controllers\Frontend\FormController records generate_lead after a stored lead and hands the event ID to the redirect so the theme pushes the browser copy with the same ID; shopify/src/.../SendPurchaseTrackingEvent listens to OrderUpdatedOrCreated. The consent snapshot gates every delivery, so a visitor who declined marketing produces no Meta event. Drivers without credentials disable themselves, which is why TRACKING_SERVER_SIDE=true is a safe default.

EncryptCookies::except() in the core provider keeps evolve_consent, _ga, _fbp and _fbc unencrypted; otherwise PHP could not read them.

Configuration ​

core/config/evolve.php → tracking (server-side, emit, queue, debug, legacy-variables, consent.policy, consent.via-cmp, consent.cmp, consent.cookiebot-id, consent.cookie-name, consent.cookie-lifetime-days), evolve.cookie-consent for the banner; platform IDs in core/config/services.php; the attribution cookie in core/config/tracking.php (footprints, duration, blacklisted paths). Http\Middleware\AttributionTracking writes footprints only when Consent::allows(Marketing); before that the token sits in the session under footprints_token, and AttributionTracking::currentToken() (cookie, else session token) is what Visit::previousVisits() and TracksAttribution match leads by (#302). Settings\TrackingSettings overlays the public IDs, the policy, server_side and cookie_banner.

Observability ​

Health\Checks\TrackingCheck fails a live site with missing or malformed GTM or GA4 IDs or a silencing TRACKING_EMIT, warns on a staging site left emitting, on an opt-in site whose only consent source is via-cmp (the Evolve cookie is never written) and on the Cookiebot driver without an ID. HealthCheckController returns the tracking block (type, emits, override, IDs, secrets-set booleans, consent, sources) that Fleet compares per environment. Tests: core/tests/Feature/Tracking/*.