Checkout / payments

Server-Side Tracking for ThriveCart

Stop losing conversions to iOS ITP, short-lived cookies, and ad-blockers. TrakSolv hosts a first-party server-side GTM container for your ThriveCart store and forwards clean, deduplicated conversions to GA4, Meta CAPI, Google Ads and more. Live in minutes, flat monthly pricing.

No credit card to startFlat monthly pricingNative ThriveCart dataLayerGA4 · Meta CAPI · Google Ads
Why it matters for ThriveCart

The pixel alone is leaking your data.

Browser-side tracking on ThriveCart loses a growing share of conversions. Here's what moving to a server-side setup fixes.

Recover conversions lost to ITP & ad-blockers

On ThriveCart, browser-side pixels are throttled by Safari/Firefox ITP, capped 7-day cookies, and blocked outright by ad-blockers and consent walls. Server-side tracking moves the measurement off the browser, so checkout completions that the pixel silently drops are recovered and reported.

First-party data, served from your own domain

TrakSolv hosts your Google Tag Manager server container on a subdomain of your ThriveCart site. Requests come from a first-party context — they survive 3rd-party cookie deprecation, last far longer than client-side cookies, and look like your own traffic, not a tracker.

Clean, deduplicated conversions to every ad platform

The ThriveCart dataLayer fires every GA4 enhanced-ecommerce event with a stable event_id, so your server-side container can forward checkout completions to GA4, Meta CAPI, Google Ads Enhanced Conversions, TikTok and more — deduplicated against the browser pixel, with higher match quality.

Step by step

How to set up server-side tracking on ThriveCart

Paste the TrakSolv loader and the ThriveCart dataLayer snippet into your store's custom-code area, save, and you're done. Every step below has the exact snippet with a copy button.

Install guide

How to install on ThriveCart

This is your install. Every step below has its exact snippet with a Copy button — go step by step. The Loader card above and DataLayer card below are reference copies of the same snippets.

  1. 1

    Open Tracking & Conversions for your Product

    Navigate

    ThriveCart → Products → edit your Product → Tracking & Conversions tab

    ThriveCart's tracking code is per-product. The same Traksolv snippet goes on BOTH the Sale page AND the Success page — it auto-detects which one it's on. Repeat for each product you sell.

  2. 2

    Loader — head half (Sale page + Success page)

    Loader • Head

    Tracking & Conversions → Sale page scripts → HEAD section → paste the Loader Head snippet. Then repeat in Success page scripts → HEAD section.

  3. 3

    Loader — body half (Sale page + Success page)

    Loader • Body

    Sale page scripts → BODY section → paste the Loader Body snippet. Then repeat in Success page scripts → BODY section.

  4. 4

    DataLayer snippet (BOTH pages, same snippet)

    DataLayer

    Sale page scripts → HEAD section → append the DataLayer snippet right after the Loader Head. Then repeat in Success page scripts → HEAD section.

    On the sale page the snippet fires view_item + hooks buy buttons for begin_checkout. On the success page it auto-reads window._thrive_order (id, invoice_id, total, currency, tax, shipping, customer.email, affiliate_id) and fires purchase. No template substitution needed.

    HTML
    <!-- Traksolv · ThriveCart DataLayer — Product → Tracking & Conversions → Sale + Success page scripts -->
    <script>
    (function (w, d) {
      w.dataLayer = w.dataLayer || [];
      var DEBUG = false;
      try {
        var q = new URLSearchParams(location.search);
        if (q.get('tcDebug') === '1') { DEBUG = true; try { localStorage.setItem('tcDebug', '1'); } catch (e) {} }
        else if (q.get('tcDebug') === '0') { DEBUG = false; try { localStorage.removeItem('tcDebug'); } catch (e) {} }
        else { try { if (localStorage.getItem('tcDebug') === '1') DEBUG = true; } catch (e) {} }
      } catch (e) {}
      w.tcDebug = DEBUG;
      if (DEBUG && w.console && w.console.log) {
        try { w.console.log('%c[Traksolv]%c dataLayer debug ON — every push will log here. Disable with ?tcDebug=0', 'color:#22c55e;font-weight:600;background:#0a1f0c;padding:2px 6px;border-radius:3px', ''); } catch (e) {}
      }
      function log() {
        if (!DEBUG || !w.console || !w.console.log) return;
        try { w.console.log.apply(w.console, ['[Traksolv]'].concat([].slice.call(arguments))); } catch (e) {}
      }
      function push(eventName, eventId, params) {
        try {
          if (params && params.ecommerce !== undefined) w.dataLayer.push({ ecommerce: null });
          var ev = { event: eventName, event_id: eventName + '.' + eventId };
          if (params) for (var k in params) if (Object.prototype.hasOwnProperty.call(params, k)) ev[k] = params[k];
          w.dataLayer.push(ev);
          log('push ' + eventName, ev);
        } catch (e) {}
      }
      function once(key) {
        try { if (sessionStorage.getItem(key)) return false; sessionStorage.setItem(key, '1'); return true; }
        catch (e) { return true; }
      }
      function textOf(sel) { var el = d.querySelector(sel); return el ? (el.textContent || '').trim() : ''; }
    
      w.tcFirePurchase = function (o) {
        try {
          if (!o || !o.orderId) return;
          if (!once('tc_purchase_' + o.orderId)) return;
          push('purchase', String(o.orderId), {
            ecommerce: {
              transaction_id: String(o.orderId),
              value:    Number(o.value) || 0,
              currency: o.currency || 'USD',
              items:    Array.isArray(o.items) ? o.items : []
            }
          });
        } catch (e) {}
      };
    
      function pageType() {
        // _thrive_order is present ONLY on the success page.
        if (w._thrive_order && w._thrive_order.order) return 'success';
        var path = (location.pathname || '').toLowerCase();
        if (/success|thank|complete|order/.test(path)) return 'success';
        if (/upsell|otop?|one[-_]?time/.test(path))    return 'upsell';
        return 'sale';
      }
      w.tcPageType = pageType;
    
      // ── Sale page: view_item + begin_checkout on buy-button click ──
      function fireViewItem() {
        try {
          var name = textOf('h1, .product-title, .product-name') || (document.title || 'product');
          var slug = (location.pathname || '/').replace(/^\//, '').split('/')[0] || 'product';
          if (!once('tc_view_item_' + slug)) return;
          push('view_item', String(slug), {
            ecommerce: {
              currency: 'USD',
              items: [{
                item_id:       String(slug),
                item_name:     name,
                item_category: 'thrivecart',
                quantity:      1
              }]
            }
          });
        } catch (e) {}
      }
      function hookBuyButtons() {
        try {
          d.addEventListener('click', function (e) {
            var btn = e.target && e.target.closest && e.target.closest('[class*="purchase"], [class*="buy"], [class*="checkout"], button[type="submit"]');
            if (!btn) return;
            var slug = (location.pathname || '/').replace(/^\//, '').split('/')[0] || 'product';
            push('begin_checkout', String(slug) + '.' + Date.now(), {
              ecommerce: {
                currency: 'USD',
                items: [{
                  item_id:       String(slug),
                  item_name:     textOf('h1, .product-title') || slug,
                  item_category: 'thrivecart',
                  quantity:      1
                }]
              }
            });
          }, true);
        } catch (e) {}
      }
    
      // ── Success page: read window._thrive_order ──
      function firePurchaseFromThrive() {
        try {
          var to = w._thrive_order;
          if (!to || !to.order) return;
          var o = to.order;
          var oid = o.invoice_id || o.id || ('thrive.' + Date.now());
          if (!once('tc_purchase_' + oid)) return;
          // ThriveCart prices are in cents — convert to currency-major units.
          var value    = (Number(o.total)    || 0) / 100;
          var tax      = (Number(o.tax)      || 0) / 100;
          var shipping = (Number(o.shipping) || 0) / 100;
          var currency = o.currency || 'USD';
    
          // ThriveCart doesn't expose a typed products array — split totals
          // between main product + bump where readable.
          var items = [];
          if (o.product && o.product.total_readable) {
            items.push({
              item_id:       'product',
              item_name:     textOf('h1, .product-title') || 'ThriveCart product',
              item_category: 'thrivecart-main',
              price:         parseFloat(String(o.product.total_readable).replace(/[^\d.]/g, '')) || 0,
              quantity:      1
            });
          }
          if (o.bump && o.bump.total_readable && parseFloat(String(o.bump.total_readable).replace(/[^\d.]/g, '')) > 0) {
            items.push({
              item_id:       'bump',
              item_name:     'Order bump',
              item_category: 'thrivecart-bump',
              price:         parseFloat(String(o.bump.total_readable).replace(/[^\d.]/g, '')) || 0,
              quantity:      1
            });
          }
          if (!items.length) {
            items.push({
              item_id:       String(o.id || oid),
              item_name:     'ThriveCart order',
              item_category: 'thrivecart',
              price:         value,
              quantity:      1
            });
          }
    
          push('purchase', String(oid), {
            ecommerce: {
              transaction_id: String(oid),
              affiliation:    'ThriveCart',
              value:          value,
              tax:            tax || undefined,
              shipping:       shipping || undefined,
              currency:       currency,
              items:          items
            },
            user_id:     (to.customer && to.customer.email) ? String(to.customer.email) : undefined,
            affiliate_id: o.affiliate_id || undefined,
            campaign_id:  (w._thrive && w._thrive.user && w._thrive.user.campaign_id) || undefined
          });
        } catch (e) {}
      }
    
      function boot() {
        var t = pageType();
        if (t === 'sale')    { fireViewItem(); hookBuyButtons(); }
        if (t === 'success')   firePurchaseFromThrive();
        // Upsell pages fire as their own success when accepted — ThriveCart
        // populates _thrive_order on the next page in that case.
      }
      if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', boot);
      else boot();
    })(window, document);
    </script>
    <!-- End Traksolv ThriveCart DataLayer -->
    
    <!-- Traksolv · DataLayer enrichment — customer lifecycle + remarketing + attribution -->
    <script>
    (function (w, d) {
      function readLT() {
        try {
          var raw = localStorage.getItem('tc_lifetime');
          var p = raw ? JSON.parse(raw) : null;
          return {
            orderCount:    (p && p.orderCount)    || 0,
            lifetimeValue: (p && p.lifetimeValue) || 0,
            firstOrderTs:  (p && p.firstOrderTs)  || null,
            lastOrderTs:   (p && p.lastOrderTs)   || null,
            lastOrderId:   (p && p.lastOrderId)   || null
          };
        } catch (e) {
          return { orderCount: 0, lifetimeValue: 0, firstOrderTs: null, lastOrderTs: null, lastOrderId: null };
        }
      }
      function writeLT(lt) {
        try { localStorage.setItem('tc_lifetime', JSON.stringify(lt)); } catch (e) {}
      }
      function tier(v) {
        if (v >= 1000) return 'vip';
        if (v >= 250)  return 'mid';
        if (v > 0)     return 'low';
        return 'guest';
      }
      function readCookie(n) {
        try {
          var m = d.cookie.match(new RegExp('(^|; )' + n + '=([^;]+)'));
          return m ? decodeURIComponent(m[2]) : null;
        } catch (e) { return null; }
      }
      function readAttr() {
        try { var r = readCookie('_tc_attr'); return r ? JSON.parse(r) : null; } catch (e) { return null; }
      }
    
      w.tcReadLifetime    = readLT;
      w.tcCustomerTier    = function (v) { return tier(v != null ? v : readLT().lifetimeValue); };
      w.tcReadAttribution = readAttr;
    
      // ── Wrap tcFirePurchase to add customer-lifecycle + attribution ──
      // before the underlying snippet pushes the purchase event. Idempotent
      // per orderId, so customer refreshing the thank-you page doesn't
      // inflate their lifetime counters.
      var orig = w.tcFirePurchase;
      if (typeof orig === 'function') {
        w.tcFirePurchase = function (o) {
          try {
            if (!o || !o.orderId) return orig.apply(this, arguments);
            var lt = readLT();
            var firstFire = lt.lastOrderId !== String(o.orderId);
            var orderCount    = firstFire ? lt.orderCount + 1 : lt.orderCount;
            var lifetimeValue = firstFire ? lt.lifetimeValue + (o.value || 0) : lt.lifetimeValue;
            var firstOrderTs  = lt.firstOrderTs || Date.now();
            var newCustomer   = orderCount === 1;
            var daysSinceFirst = Math.floor((Date.now() - firstOrderTs) / 86400000);
    
            if (firstFire) {
              writeLT({
                orderCount:    orderCount,
                lifetimeValue: lifetimeValue,
                firstOrderTs:  firstOrderTs,
                lastOrderTs:   Date.now(),
                lastOrderId:   String(o.orderId)
              });
            }
    
            var attr = readAttr();
            // Build the enriched order. Preserve every original field; add
            // customer + attribution on top.
            var enriched = {};
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) enriched[k] = o[k];
            enriched.new_customer            = newCustomer;
            enriched.customer_type           = newCustomer ? 'new' : 'returning';
            enriched.customer_status         = 'customer';
            enriched.customer_lifetime_value = Math.round(lifetimeValue * 100) / 100;
            enriched.customer_order_count    = orderCount;
            enriched.customer_aov            = orderCount > 0 ? Math.round((lifetimeValue / orderCount) * 100) / 100 : 0;
            enriched.purchase_number         = orderCount;
            enriched.days_since_first_order  = daysSinceFirst;
            enriched.value_tier              = tier(lifetimeValue);
            if (attr) enriched.attribution = {
              landing:   attr.u   || null,
              referrer:  attr.r   || null,
              utm:       attr.utm || null,
              click_ids: attr.cid || null
            };
            return orig.call(this, enriched);
          } catch (e) {
            try { return orig.apply(this, arguments); } catch (e2) {}
          }
        };
      }
    
      // ── tc_visitor_context — emits visitor lifecycle + remarketing on
      //    every page-view. GTM / GA4 audience triggers + Google Ads
      //    dynamic remarketing tag read these.
      function visitorContext() {
        try {
          var lt = readLT();
          var attr = readAttr();
          var pageType = (typeof w.tcPageType === 'function') ? (w.tcPageType() || 'other') : 'other';
          w.dataLayer = w.dataLayer || [];
          w.dataLayer.push({
            event:                   'tc_visitor_context',
            event_id:                'tc_visitor_context.' + Date.now() + '.' + Math.random().toString(36).slice(2, 6),
            customer_status:         lt.orderCount > 0 ? 'returning_customer' : 'guest',
            customer_type:           lt.orderCount > 0 ? 'returning' : 'guest',
            value_tier:              tier(lt.lifetimeValue),
            customer_lifetime_value: Math.round(lt.lifetimeValue * 100) / 100,
            customer_order_count:    lt.orderCount,
            days_since_first_order:  lt.firstOrderTs ? Math.floor((Date.now() - lt.firstOrderTs) / 86400000) : null,
            ecomm_pagetype:          pageType,
            attribution: attr ? {
              landing:   attr.u   || null,
              referrer:  attr.r   || null,
              utm:       attr.utm || null,
              click_ids: attr.cid || null
            } : null
          });
        } catch (e) {}
      }
      if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', visitorContext);
      else visitorContext();
    })(window, document);
    </script>
    <!-- End Traksolv enrichment -->
  5. 5

    Save and test

    Save

    Save → process a test order to verify both view_item (sale page) and purchase (success page) fire

    ?tcDebug=1 on the success page will log _thrive_order alongside the purchase push.

The loader Head + Body snippets are generated for your container inside the TrakSolv dashboard. Create a free account to grab them, or see the full how-it-works walkthrough.

FAQ

Server-side tracking for ThriveCart, answered

Does server-side tracking work with ThriveCart?

Yes. TrakSolv works with ThriveCart via a lightweight loader snippet plus a ThriveCart-specific dataLayer that fires every GA4 enhanced-ecommerce event. You paste it once in ThriveCart's custom-code area and conversions start flowing through your own server-side GTM container.

How do I install TrakSolv on ThriveCart?

Add the TrakSolv loader (head + body) and the ThriveCart dataLayer snippet to your store's custom-code settings, then save. The full step-by-step is on this page. Most ThriveCart merchants are live in a few minutes — no developer required.

What can I track server-side on ThriveCart?

Page views, product views, checkout completions, begin-checkout and more — every standard GA4 enhanced-ecommerce event, forwarded server-side to GA4, Meta CAPI, Google Ads and other destinations with deduplication.

How much does server-side tracking for ThriveCart cost?

TrakSolv uses flat monthly pricing — one event sent to many destinations still counts as one event, and paid plans never auto-pause at quota. You can start free and go live in minutes.

Recover your ThriveCart conversions — start free.

Spin up a first-party server-side GTM container for ThriveCart and watch your match quality climb. No credit card, live in minutes.

WhatsApp
Server-Side Tracking for ThriveCart — TrakSolv | Traksolv