Documentation Ticketmatic
...
User tracking
User tracking - GA4 Tutorial
ticketmatic server side events to google analytics 4 step by step tutorial this tutorial walks you through sending ticketmatic's server side user tracking events to google analytics 4 (ga4) using the measurement protocol by the end, you'll have a pipeline that captures every webshop action (add to cart, checkout, purchase, etc ) and forwards it to ga4 for reporting setting this up requires technical knowledge and you might need the help of a partner to implement this for your specific situation good candidates are your website builder your website ticketing integrator your marketing partner architecture overview the flow works like this a visitor lands on your website, where your analytics script assigns them a ga4 client id when the visitor opens a ticketmatic widget or sales flow, you pass that client id as the ref parameter ticketmatic records every user action and stores it in their eventstream api your server polls the ticketmatic eventstream, picks up new events, and forwards them to ga4 via the measurement protocol visitor on your site ──► ticketmatic widget (?ref=ga client id) │ ▼ ticketmatic eventstream api │ (your server polls) │ ▼ ga4 measurement protocol api │ ▼ google analytics 4 reports prerequisites before you begin, make sure you have a ticketmatic account with api access (access key + secret key) a google analytics 4 property a server with php 7 4+ and the curl extension enabled (standard on most hosting) basic familiarity with rest apis step 1 create a ga4 measurement protocol api secret the measurement protocol requires an api secret to authenticate requests open https //analytics google com and navigate to your ga4 property go to admin (gear icon) > data streams click on your web data stream scroll down and click measurement protocol api secrets click create and give it a nickname (e g "ticketmatic server events") copy both the api secret and the measurement id (format g xxxxxxxxxx ) you'll need them later important keep the api secret private never expose it in client side code step 2 pass the ga4 client id as the ticketmatic ref parameter ticketmatic's user tracking is activated by appending a ref parameter to any widget or sales flow url the ref value ties server side events back to a specific visitor we'll use the ga4 client id for this purpose, which allows ga4 to stitch server side events to the correct user session 2a read the ga4 client id from the browser the ga4 client id is stored in the ga cookie you can extract it with javascript on your website function getga4clientid() { // option 1 read from the ga cookie const cookie = document cookie split('; ') find(row => row\ startswith(' ga=')); if (cookie) { // ga cookie format ga1 1 xxxxxxxxxx xxxxxxxxxx // the client id is the last two number groups joined by a dot const parts = cookie split(' '); return parts slice(2) join(' '); } // option 2 use the gtag api (if available) return new promise((resolve) => { gtag('get', 'g xxxxxxxxxx', 'client id', (clientid) => { resolve(clientid); }); }); } 2b append ref to your ticketmatic links when generating links to ticketmatic widgets or sales flows, append ?ref=\<client id> (or \&ref=\<client id> if there are already query parameters) const clientid = getga4clientid(); const ticketmaticurl = `https //apps ticketmatic com/widgets/demo/flow/saleslink?event=130716474767\&l=en\&ref=${clientid}`; // use this url for your "buy tickets" button the ref parameter does not need to be included in the widget signature, so you can safely append it to any pre generated link note if you don't pass a ref parameter, ticketmatic will not track user actions for that session step 3 poll the ticketmatic eventstream api ticketmatic stores all tracked user actions in a server side eventstream your job is to poll this endpoint periodically and process new events 3a understand the eventstream response each call to the eventstream returns true 330,331 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type each event object contains true 330,331 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type 3b available event types true 330,331 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type for a detailed description of each event type and its payload, see the https //docs ticketmatic com/en/user tracking 3c polling logic here's a basic example using curl \# first call (no id parameter = returns oldest available events) curl u '\<accesskey> \<secretkey>' \\ 'https //apps ticketmatic com/api/1/\<accountname>/eventstream' \# subsequent calls (pass nextid from previous response) curl u '\<accesskey> \<secretkey>' \\ 'https //apps ticketmatic com/api/1/\<accountname>/eventstream?id=\<nextid>' the polling loop should follow this pattern call the endpoint process the results if moreresults is true , immediately call again with nextid if moreresults is false , wait 5 60 seconds, then call again important ticketmatic stores eventstream items for 4 hours only make sure your polling process runs frequently enough to avoid missing events step 4 map ticketmatic events to ga4 events below is a recommended mapping from ticketmatic event types to ga4 recommended ecommerce events true 220,220,221 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type you can also send webshop\ checkout initiatepayment and webshop\ checkout adddeliveryinfo as custom events if you want that level of granularity refer to the https //docs ticketmatic com/en/user tracking for the full payload structure of each event type, so you can extract the right fields for your ga4 parameters step 5 send events to ga4 via the measurement protocol 5a the ga4 measurement protocol endpoint send post requests to https //www google analytics com/mp/collect?measurement id=g xxxxxxxxxx\&api secret=your api secret 5b request body structure { "client id" "\<the ref value from ticketmatic>", "events" \[ { "name" "add to cart", "params" { "currency" "eur", "value" 45 00, "items" \[ { "item id" "evt 12345", "item name" "concert main hall cat 1", "item category" "tickets", "price" 45 00, "quantity" 1 } ] } } ] } 5c full implementation example (php) \<?php / ticketmatic eventstream → ga4 measurement protocol bridge run as a cli script php ticketmatic ga4 bridge php or via cron job php /path/to/ticketmatic ga4 bridge php / // configuration define('tm access key', 'your ticketmatic access key'); define('tm secret key', 'your ticketmatic secret key'); define('tm account', 'your account shortname'); define('ga4 measurement id', 'g xxxxxxxxxx'); define('ga4 api secret', 'your ga4 api secret'); // file to persist the nextid between runs define('state file', dir '/eventstream state json'); // ============================================================ // ga4 measurement protocol // ============================================================ / send a single event to ga4 via the measurement protocol / function sendtoga4(string $clientid, string $eventname, array $eventparams) void { $url = sprintf( 'https //www google analytics com/mp/collect?measurement id=%s\&api secret=%s', ga4 measurement id, ga4 api secret ); $payload = json encode(\[ 'client id' => $clientid, 'events' => \[\[ 'name' => $eventname, 'params' => array merge(\[ 'engagement time msec' => '1', // required for the event to appear in reports 'session id' => $clientid, // helps stitch to the existing session ], $eventparams), ]], ]); $ch = curl init($url); curl setopt array($ch, \[ curlopt post => true, curlopt postfields => $payload, curlopt httpheader => \['content type application/json'], curlopt returntransfer => true, curlopt timeout => 10, ]); $response = curl exec($ch); $httpcode = curl getinfo($ch, curlinfo http code); curl close($ch); if ($httpcode < 200 || $httpcode >= 300) { error log("ga4 error (http $httpcode) $response"); } } // ============================================================ // event mapping // ============================================================ / map a single ticketmatic event to a ga4 event and send it / function mapandsend(array $tmevent) void { $data = $tmevent\['data'] ?? \[]; $ref = $data\['ref'] ?? null; if (empty($ref)) { return; // no ref = can't link to ga4 user } $type = $tmevent\['type'] ?? ''; $order = $data\['order'] ?? \[]; $currency = $order\['currency'] ?? 'eur'; switch ($type) { case 'webshop\ add tickets' sendtoga4($ref, 'add to cart', \[ 'currency' => $currency, 'value' => calculatevalue($data\['tickets'] ?? \[]), 'items' => mapticketstoitems($data\['tickets'] ?? \[]), ]); break; case 'webshop\ remove tickets' sendtoga4($ref, 'remove from cart', \[ 'currency' => $currency, 'value' => calculatevalue($data\['tickets'] ?? \[]), 'items' => mapticketstoitems($data\['tickets'] ?? \[]), ]); break; case 'webshop\ add products' sendtoga4($ref, 'add to cart', \[ 'currency' => $currency, 'value' => calculatevalue($data\['products'] ?? \[]), 'items' => mapproductstoitems($data\['products'] ?? \[]), ]); break; case 'webshop\ remove products' sendtoga4($ref, 'remove from cart', \[ 'currency' => $currency, 'value' => calculatevalue($data\['products'] ?? \[]), 'items' => mapproductstoitems($data\['products'] ?? \[]), ]); break; case 'webshop\ checkout start' sendtoga4($ref, 'begin checkout', \[ 'currency' => $currency, 'value' => (float) ($order\['totalamount'] ?? 0), ]); break; case 'webshop\ checkout addpaymentinfo' sendtoga4($ref, 'add payment info', \[ 'currency' => $currency, 'value' => (float) ($order\['totalamount'] ?? 0), ]); break; case 'webshop\ checkout confirm' sendtoga4($ref, 'purchase', \[ 'transaction id' => (string) ($order\['orderid'] ?? $tmevent\['id'] ?? ''), 'currency' => $currency, 'value' => (float) ($order\['totalamount'] ?? 0), ]); break; case 'webshop\ pageview' sendtoga4($ref, 'page view', \[ 'page title' => $data\['page'] ?? 'ticketmatic', ]); break; default error log("skipping unknown event type $type"); } } // ============================================================ // helper functions // ============================================================ function calculatevalue(array $items) float { return array reduce($items, function (float $sum, array $item) { return $sum + (float) ($item\['price'] ?? 0); }, 0 0); } function mapticketstoitems(array $tickets) array { return array map(function (array $t) { return \[ 'item id' => (string) ($t\['tickettypepriceid'] ?? $t\['id'] ?? ''), 'item name' => $t\['eventname'] ?? $t\['tickettypename'] ?? 'ticket', 'item category' => 'tickets', 'price' => (float) ($t\['price'] ?? 0), 'quantity' => 1, ]; }, $tickets); } function mapproductstoitems(array $products) array { return array map(function (array $p) { return \[ 'item id' => (string) ($p\['id'] ?? ''), 'item name' => $p\['name'] ?? 'product', 'item category' => 'products', 'price' => (float) ($p\['price'] ?? 0), 'quantity' => 1, ]; }, $products); } // ============================================================ // state persistence // ============================================================ function loadnextid() ?string { if (!file exists(state file)) { return null; } $state = json decode(file get contents(state file), true); return $state\['nextid'] ?? null; } function savenextid(string $nextid) void { file put contents(state file, json encode(\['nextid' => $nextid])); } // ============================================================ // ticketmatic eventstream polling // ============================================================ function polleventstream() void { $nextid = loadnextid(); // we loop as long as there are more results, then exit // the cron job will re invoke this script on the next interval do { $url = sprintf( 'https //apps ticketmatic com/api/1/%s/eventstream%s', tm account, $nextid ? '?id=' urlencode($nextid) '' ); $ch = curl init($url); curl setopt array($ch, \[ curlopt userpwd => tm access key ' ' tm secret key, curlopt returntransfer => true, curlopt timeout => 30, ]); $response = curl exec($ch); $httpcode = curl getinfo($ch, curlinfo http code); curl close($ch); if ($httpcode !== 200) { error log("ticketmatic api error (http $httpcode) $response"); return; } $data = json decode($response, true); if (!is array($data)) { error log("invalid json from ticketmatic eventstream"); return; } // process each event foreach ($data\['results'] ?? \[] as $event) { mapandsend($event); } // persist the cursor $nextid = $data\['nextid'] ?? null; if ($nextid) { savenextid($nextid); } $moreresults = $data\['moreresults'] ?? false; } while ($moreresults); echo "poll complete processed " count($data\['results'] ?? \[]) " event(s) \n"; } // run polleventstream(); running the script you can either run this as a long lived cli process with a sleep() loop, or (recommended) schedule it as a cron job that runs every minute the script persists its cursor to eventstream state json , so each invocation picks up where the last one left off php /path/to/ticketmatic ga4 bridge php >> /var/log/tm ga4 log 2>&1 step 6 validate your setup before going live, use the ga4 debug endpoint to validate your events 6a use the validation endpoint replace /mp/collect with /debug/mp/collect in the url https //www google analytics com/debug/mp/collect?measurement id=g xxxxxxxxxx\&api secret=your api secret this returns a json response telling you whether the event was valid or what errors were found no data is actually sent to your ga4 property 6b check ga4 realtime reports open your ga4 property in google analytics go to reports > realtime trigger a test action in a ticketmatic widget (make sure the ref parameter is set) within a few seconds, you should see the event appear in the realtime report 6c verify ecommerce reports after events have been flowing for 24 48 hours go to reports > monetization > ecommerce purchases you should see transactions attributed to the purchase events step 7 deploy to production hosting options your polling script needs to run continuously good options include a cron job on your existing php hosting (simplest option — run every minute) a long lived cli process with a while(true) / sleep() loop on a server a cloud function on a schedule (e g aws lambda with a php runtime layer, or google cloud run) a container (docker with php cli) on any cloud platform persistence store the nextid value between runs (e g in a database, a file, or an environment variable) so you don't reprocess old events or miss events after a restart error handling recommendations retry failed ga4 calls with exponential backoff log all events for debugging and auditing monitor your polling interval to ensure you process events within the 4 hour window handle unknown event types gracefully since ticketmatic may add new types in the future gdpr and privacy considerations ticketmatic's user tracking is entirely opt in a few things to keep in mind only pass the ref parameter after obtaining valid consent from the user ticketmatic stores tracking data exclusively in their own systems and does not inject third party trackers or non essential cookies since you're forwarding data to google analytics, make sure your privacy policy covers this data flow consider using ga4's consent mode to respect user preferences on the google side as well summary checklist ga4 measurement protocol api secret created ga4 measurement id noted website reads ga4 client id from ga cookie ticketmatic widget/sales flow urls include ?ref=\<client id> server side script polls ticketmatic eventstream events are mapped to ga4 ecommerce events events are sent to ga4 measurement protocol validation endpoint confirms events are well formed realtime reports show incoming events nextid is persisted between polling runs gdpr consent is handled before tracking is activated references https //docs ticketmatic com/en/user tracking , https //developers google com/analytics/devguides/collection/protocol/ga4/sending events , https //developers google com/analytics/devguides/collection/ga4/set up ecommerce