Server-Side Tracking for Webflow
Stop losing conversions to iOS ITP, short-lived cookies, and ad-blockers. TrakSolv hosts a first-party server-side GTM container for your Webflow store and forwards clean, deduplicated conversions to GA4, Meta CAPI, Google Ads and more. Live in minutes, flat monthly pricing.
The pixel alone is leaking your data.
Browser-side tracking on Webflow loses a growing share of conversions. Here's what moving to a server-side setup fixes.
Recover conversions lost to ITP & ad-blockers
On Webflow, 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 leads and purchases 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 Webflow 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 Webflow dataLayer fires every GA4 enhanced-ecommerce event with a stable event_id, so your server-side container can forward leads and purchases to GA4, Meta CAPI, Google Ads Enhanced Conversions, TikTok and more — deduplicated against the browser pixel, with higher match quality.
How to set up server-side tracking on Webflow
Paste the TrakSolv loader and the Webflow 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 Webflow
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
Open Project Settings
NavigateWebflow Designer → top-right cog → Project Settings → Custom Code
- 2
Loader — head half
Loader • HeadCustom Code → Head Code
Paste the Head snippet from the Custom Loader card.
- 3
Loader — body half + DataLayer
Loader • BodyCustom Code → Footer Code (paste Body snippet FIRST)
In the same Footer Code box, paste the Loader Body snippet first.
- 4
Append the DataLayer snippet right after
DataLayerCustom Code → Footer Code (paste DataLayer immediately after the Body snippet)
Both live in the same Footer Code field, stacked. Webflow's Footer Code character limit is 50k — plenty for both.
HTML<!-- Traksolv · Webflow DataLayer (advanced) — Project Settings → Footer Code --> <script> (function (w, d) { // ── Init + helpers ─────────────────────────────────────────────── w.dataLayer = w.dataLayer || []; var DEBUG = false; try { DEBUG = new URLSearchParams(location.search).get('tcDebug') === '1'; } catch (e) {} function log(msg, payload) { if (DEBUG && w.console && console.log) console.log('[Traksolv]', msg, payload || ''); } 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 money(v) { return parseFloat(String(v == null ? '' : v).replace(/[^\d.\-]/g, '')) || 0; } function textOf(sel, root) { var el = (root || d).querySelector(sel); return el ? (el.textContent || '').trim() : ''; } function attrOf(sel, attr, root) { var el = (root || d).querySelector(sel); return el ? (el.getAttribute(attr) || '') : ''; } // Currency: Webflow tags price elements with data-commerce-currency-code. // Fall back to the meta tag or the og:price:currency tag. function getCurrency() { var c = attrOf('[data-commerce-currency-code]', 'data-commerce-currency-code') || attrOf('meta[property="og:price:currency"]', 'content') || attrOf('meta[itemprop="priceCurrency"]', 'content'); return c || 'USD'; } // Read a Webflow Memberships logged-in user id from cookies if any. function getMemberId() { try { var m = d.cookie.match(/(?:^|; )wf-auth-token=([^;]+)/); if (!m) return null; // Webflow Memberships sets a JWT; we don't decode it, just // surface a stable id so downstream tags can hash + use as // external_id. The token itself is the id. return m[1].slice(0, 32); } catch (e) { return null; } } // ── Universal escape hatch for CUSTOM thank-you / order pages ──── 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: o.value || 0, currency: o.currency || getCurrency(), tax: o.tax, shipping: o.shipping, items: Array.isArray(o.items) ? o.items : [] } }); } catch (e) {} }; // ── Typed merchant escape hatches ──────────────────────────────── // For any event auto-detect missed, merchant can call these from // Designer-embedded scripts or Custom Code blocks. w.tcDataLayer = { viewItem: function (item, currency) { if (!item || !item.item_id) return; if (!once('tc_view_item_' + item.item_id)) return; push('view_item', String(item.item_id), { ecommerce: { currency: currency || getCurrency(), value: item.price || 0, items: [item] } }); }, addToCart: function (item, currency) { if (!item || !item.item_id) return; push('add_to_cart', item.item_id + '.' + Date.now(), { ecommerce: { currency: currency || getCurrency(), value: (item.price || 0) * (item.quantity || 1), items: [item] } }); }, removeFromCart: function (item, currency) { if (!item || !item.item_id) return; push('remove_from_cart', item.item_id + '.' + Date.now(), { ecommerce: { currency: currency || getCurrency(), value: (item.price || 0) * (item.quantity || 1), items: [item] } }); }, viewCart: function (items, currency, value) { if (!items || !items.length) return; var key = items.map(function (i) { return i.item_id + ':' + (i.quantity || 1); }).join('|'); if (!once('tc_view_cart_' + key)) return; push('view_cart', key.slice(0, 64), { ecommerce: { currency: currency || getCurrency(), value: value || 0, items: items } }); }, beginCheckout: function (items, currency, value) { if (!items || !items.length) return; var key = items.map(function (i) { return i.item_id + ':' + (i.quantity || 1); }).join('|'); if (!once('tc_begin_checkout_' + key)) return; push('begin_checkout', key.slice(0, 64), { ecommerce: { currency: currency || getCurrency(), value: value || 0, items: items } }); } }; // ── User identification (when Webflow Memberships is in use) ───── (function () { var mid = getMemberId(); if (!mid) return; if (!once('tc_user_identified_' + mid)) return; w.dataLayer.push({ event: 'tc_user_identified', user_id: mid, user_data: { external_id: mid } }); log('user identified', mid); })(); // ── Cart helpers ───────────────────────────────────────────────── function readCartLine(row) { var name = textOf('.w-commerce-commercecartitemtitle, [data-node-type="commerce-cart-item-title"]', row); var qtyEl = row.querySelector('.w-commerce-commercecartquantity, [data-node-type="commerce-cart-quantity-input"]'); var price = money(textOf('.w-commerce-commercecartitemprice, [data-node-type="commerce-cart-item-price"], .w-commerce-commerceboldtextblock', row)); var sku = row.getAttribute('data-commerce-sku-id') || ((row.querySelector('[data-commerce-sku-id]') || {}).getAttribute && row.querySelector('[data-commerce-sku-id]').getAttribute('data-commerce-sku-id')) || ''; return { item_id: sku || name || ('item.' + Math.random().toString(36).slice(2, 8)), item_sku: sku || undefined, item_name: name, price: price, quantity: qtyEl ? (parseInt((qtyEl.value || qtyEl.textContent || '1'), 10) || 1) : 1 }; } function readCart() { var rows = d.querySelectorAll('.w-commerce-commercecartitem'); var items = []; for (var i = 0; i < rows.length; i++) items.push(readCartLine(rows[i])); return items; } function cartValue(items) { var v = 0; for (var i = 0; i < items.length; i++) v += (items[i].price || 0) * (items[i].quantity || 1); return v; } // Read the rich item shape on the current product page (used by // view_item AND by add_to_cart for richer data than the cart line). function readCurrentProduct() { var form = d.querySelector('form.w-commerce-commerceaddtocartform, [data-node-type="commerce-add-to-cart-form"]'); if (!form) return null; var pid = form.getAttribute('data-commerce-product-id') || attrOf('[data-commerce-product-id]', 'data-commerce-product-id'); if (!pid) return null; // Selected SKU updates as the variant <select> changes; read fresh. var sku = (form.querySelector('input[name="commerceItemId"], [data-commerce-sku-id]') || {}).value || (form.querySelector('[data-commerce-sku-id]') ? form.querySelector('[data-commerce-sku-id]').getAttribute('data-commerce-sku-id') : ''); var name = textOf('[data-node-type="commerce-product-default-title"], h1'); var price = money(textOf('[data-node-type="commerce-product-default-price"], .product-price, .price')); // Selected variant text — pull from any <select> values in the form. var selects = form.querySelectorAll('select'); var variantParts = []; for (var i = 0; i < selects.length; i++) { var s = selects[i]; var opt = s.options ? s.options[s.selectedIndex] : null; if (opt && opt.value && opt.value !== '') variantParts.push(opt.textContent || opt.value); } // CMS-bound brand / category if the Designer added them. var brand = textOf('[data-product-brand], .product-brand, [data-brand]'); var category = textOf('[data-product-category], .product-category, .breadcrumb-category'); return { pid: pid, sku: sku || pid, name: name, price: price, variant: variantParts.join(' / ') || undefined, brand: brand || undefined, category: category || undefined }; } // ── Page detection ─────────────────────────────────────────────── function pageType() { var u; try { u = new URL(location.href); } catch (e) { u = { searchParams: { get: function () { return ''; } }, pathname: location.pathname }; } var path = (u.pathname || location.pathname || '').toLowerCase(); if (u.searchParams.get('orderId') || /\/order-confirmation/i.test(location.pathname) || d.querySelector('.w-commerce-commerceorderconfirmationitemslist, [data-node-type="commerce-order-confirmation-container"]')) return 'order'; if (d.querySelector('.w-commerce-commercecheckoutformcontainer, [data-node-type="commerce-checkout-form-container"]')) return 'checkout'; var hasCartList = !!d.querySelector('.w-commerce-commercecartlist, [data-node-type="commerce-cart-list"]'); if (/\/cart\b/.test(path) && hasCartList) return 'cart'; if (/\/product\/|\/products\//.test(path) || d.querySelector('[data-node-type="commerce-product-default-title"]')) return 'product'; if (/\/category\/|\/categories\//.test(path) || d.querySelector('.w-commerce-commercecollectionsection .w-dyn-item, .product-list .w-dyn-item, .collection-list .w-dyn-item')) return 'category'; return null; } // ── Per-page fires ─────────────────────────────────────────────── function fireViewItem() { try { var p = readCurrentProduct(); if (!p) return; if (!once('tc_view_item_' + p.pid)) return; push('view_item', p.pid, { ecommerce: { currency: getCurrency(), value: p.price, items: [{ item_id: String(p.pid), item_sku: p.sku || undefined, item_name: p.name, item_brand: p.brand, item_category: p.category, item_variant: p.variant, price: p.price, quantity: 1 }] } }); // Re-fire if variant select changes — Webflow updates the form's // data-commerce-sku-id; we'd want a new view_item only if the // SKU id actually changed (= different variant). var form = d.querySelector('form.w-commerce-commerceaddtocartform'); if (form) { form.addEventListener('change', function () { var np = readCurrentProduct(); if (!np || np.sku === p.sku) return; if (!once('tc_view_item_v_' + np.sku)) return; push('view_item', np.sku, { ecommerce: { currency: getCurrency(), value: np.price, items: [{ item_id: String(np.pid), item_sku: np.sku, item_name: np.name, item_variant: np.variant, price: np.price, quantity: 1 }] } }); }); } } catch (e) {} } function fireViewItemList() { try { var cards = d.querySelectorAll('.w-commerce-commercecollectionsection .w-dyn-item, .product-list .w-dyn-item, .collection-list .w-dyn-item'); if (!cards.length) return; var listId = location.pathname; var listName = textOf('h1') || listId; // Build the item map once; fire impressions as cards scroll in. function readCard(node, i) { var f = node.querySelector('form.w-commerce-commerceaddtocartform'); var pid = (f && f.getAttribute('data-commerce-product-id')) || node.getAttribute('data-commerce-product-id') || (node.querySelector('a[href*="/product/"]') ? (node.querySelector('a[href*="/product/"]').getAttribute('href') || '').split('/').pop() : ''); if (!pid) return null; return { item_id: String(pid), item_name: textOf('h2, h3, h4, .product-name, [data-product-name]', node), price: money(textOf('.product-price, .price, [data-product-price]', node)), index: i, item_list_id: listId, item_list_name: listName }; } // Fire one consolidated view_item_list on page load (the first 25 // items visible / above the fold) + an impressions stream for the // rest as they scroll into view. var initial = []; for (var i = 0; i < Math.min(cards.length, 25); i++) { var it = readCard(cards[i], i); if (it) initial.push(it); } if (initial.length && once('tc_view_item_list_' + listId)) { push('view_item_list', listId, { ecommerce: { item_list_id: listId, item_list_name: listName, items: initial } }); } // IntersectionObserver-driven impressions for the rest. if ('IntersectionObserver' in w) { var fired = {}; var io = new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (!en.isIntersecting) return; var node = en.target; var idx = parseInt(node.getAttribute('data-tc-idx') || '0', 10); if (fired[idx]) return; fired[idx] = true; var item = readCard(node, idx); if (!item) return; // Skip if already in the initial batch. if (idx < 25) return; push('view_item_list', listId + '.' + idx, { ecommerce: { item_list_id: listId, item_list_name: listName, items: [item] } }); }); }, { threshold: 0.5 }); for (var j = 25; j < cards.length; j++) { cards[j].setAttribute('data-tc-idx', String(j)); io.observe(cards[j]); } } // ── select_item — click anywhere inside a product card link. ── d.addEventListener('click', function (e) { try { var card = e.target && e.target.closest && e.target.closest('.w-commerce-commercecollectionsection .w-dyn-item, .product-list .w-dyn-item, .collection-list .w-dyn-item'); if (!card) return; var link = e.target.closest('a[href*="/product/"]'); if (!link) return; var cards2 = d.querySelectorAll('.w-commerce-commercecollectionsection .w-dyn-item, .product-list .w-dyn-item, .collection-list .w-dyn-item'); var idx = -1; for (var k = 0; k < cards2.length; k++) if (cards2[k] === card) { idx = k; break; } var it = readCard(card, idx); if (!it) return; push('select_item', it.item_id + '.' + Date.now(), { ecommerce: { item_list_id: listId, item_list_name: listName, items: [it] } }); } catch (e) {} }, true); } catch (e) {} } function fireViewCart() { try { var items = readCart(); if (!items.length) return; var key = items.map(function (i) { return i.item_id + ':' + i.quantity; }).join('|') || 'cart'; if (!once('tc_view_cart_' + key)) return; push('view_cart', key.slice(0, 64), { ecommerce: { currency: getCurrency(), value: cartValue(items), items: items } }); } catch (e) {} } function fireBeginCheckout() { try { var items = readCart(); if (!items.length) return; var key = items.map(function (i) { return i.item_id + ':' + i.quantity; }).join('|') || 'checkout'; if (!once('tc_begin_checkout_' + key)) return; push('begin_checkout', key.slice(0, 64), { ecommerce: { currency: getCurrency(), value: cartValue(items), items: items } }); // Observe checkout step completion → add_shipping_info / // add_payment_info. Webflow renders the wrappers as the user // fills them in. var seenShip = false, seenPay = false; var obs = new MutationObserver(function () { try { if (!seenShip) { var sh = d.querySelector('.w-commerce-commercecheckoutshippingsummarywrapper, [data-node-type="commerce-checkout-shipping-summary-wrapper"]'); if (sh && (sh.textContent || '').replace(/\s+/g, '').length > 4) { seenShip = true; push('add_shipping_info', key.slice(0, 64) + '.s', { ecommerce: { currency: getCurrency(), value: cartValue(items), items: items } }); } } if (!seenPay) { var pa = d.querySelector('.w-commerce-commercecheckoutpaymentsummarywrapper, [data-node-type="commerce-checkout-payment-summary-wrapper"]'); if (pa && (pa.textContent || '').replace(/\s+/g, '').length > 4) { seenPay = true; push('add_payment_info', key.slice(0, 64) + '.p', { ecommerce: { currency: getCurrency(), value: cartValue(items), items: items } }); } } } catch (e) {} }); obs.observe(d.documentElement, { childList: true, subtree: true, characterData: true }); } catch (e) {} } function firePurchase() { try { var u; try { u = new URL(location.href); } catch (e) { return; } var orderId = u.searchParams.get('orderId') || u.searchParams.get('wf_order_id') || attrOf('[data-order-id]', 'data-order-id') || textOf('[data-node-type="commerce-order-confirmation-order-number"]'); if (!orderId) return; if (!once('tc_purchase_' + orderId)) return; // Try every common total selector Webflow ships. var revenue = money( textOf('[data-node-type="commerce-order-confirmation-order-total"]') || textOf('.w-commerce-commerceorderconfirmationordertotal') || textOf('.order-total') ); var tax = money( textOf('[data-node-type="commerce-order-confirmation-tax-value"]') || textOf('.w-commerce-commerceorderconfirmationordertax') ); var shipping = money( textOf('[data-node-type="commerce-order-confirmation-shipping-value"]') || textOf('.w-commerce-commerceorderconfirmationordershipping') ); var items = []; var rows = d.querySelectorAll('.w-commerce-commerceorderconfirmationline, .order-line-item'); for (var i = 0; i < rows.length; i++) { var r = rows[i]; var name = textOf('.w-commerce-commerceorderconfirmationitemtitle, [data-node-type="commerce-order-confirmation-item-title"], .order-item-title', r); var price = money(textOf('.w-commerce-commerceorderconfirmationitemprice, [data-node-type="commerce-order-confirmation-item-price"]', r)); var qty = parseInt(((textOf('.w-commerce-commerceorderconfirmationquantity, [data-node-type="commerce-order-confirmation-item-quantity"]', r) || '1').replace(/\D/g, '')) || '1', 10); items.push({ item_id: name || ('item.' + i), item_name: name, price: price, quantity: qty }); } push('purchase', orderId, { ecommerce: { transaction_id: String(orderId), value: revenue, currency: getCurrency(), tax: tax || undefined, shipping: shipping || undefined, items: items } }); } catch (e) {} } // ── Cart fly-out open detection ─────────────────────────────────── // Webflow's cart fly-out toggles data-node-state="open" on the // container when it opens. Fire view_cart on each open. function watchCartFlyout() { var wrap = d.querySelector('.w-commerce-commercecartcontainerwrapper, [data-node-type="commerce-cart-container-wrapper"]'); if (!wrap) return; var obs = new MutationObserver(function () { try { var state = wrap.getAttribute('data-node-state') || wrap.getAttribute('aria-expanded'); if (state === 'open' || state === 'true') { var items = readCart(); if (items.length) { var key = items.map(function (i) { return i.item_id + ':' + i.quantity; }).join('|'); if (once('tc_view_cart_fly_' + key)) { push('view_cart', key.slice(0, 64), { ecommerce: { currency: getCurrency(), value: cartValue(items), items: items } }); } } } } catch (e) {} }); obs.observe(wrap, { attributes: true }); } // ── Cart diff observer: add_to_cart / remove_from_cart ────────── var prev = []; function syncCart() { try { var now = readCart(); // Removals. for (var i = 0; i < prev.length; i++) { var p = prev[i]; var match = null; for (var j = 0; j < now.length; j++) if (now[j].item_id === p.item_id) { match = now[j]; break; } var prevQ = p.quantity || 0; var nowQ = match ? (match.quantity || 0) : 0; if (nowQ < prevQ) { var removed = { item_id: p.item_id, item_name: p.item_name, price: p.price, quantity: prevQ - nowQ }; push('remove_from_cart', p.item_id + '.' + Date.now(), { ecommerce: { currency: getCurrency(), value: (p.price || 0) * removed.quantity, items: [removed] } }); } } // Adds — but use the current page's product info if we're on the // product page, for richer item data than the cart line. for (var k = 0; k < now.length; k++) { var n = now[k]; var prevMatch = null; for (var l = 0; l < prev.length; l++) if (prev[l].item_id === n.item_id) { prevMatch = prev[l]; break; } var oldQ = prevMatch ? (prevMatch.quantity || 0) : 0; var newQ = n.quantity || 0; if (newQ > oldQ) { var richItem = null; var cur = pageType() === 'product' ? readCurrentProduct() : null; if (cur && (cur.sku === n.item_id || cur.pid === n.item_id)) { richItem = { item_id: String(cur.pid), item_sku: cur.sku, item_name: cur.name, item_brand: cur.brand, item_category: cur.category, item_variant: cur.variant, price: cur.price, quantity: newQ - oldQ }; } else { richItem = { item_id: n.item_id, item_name: n.item_name, price: n.price, quantity: newQ - oldQ }; } push('add_to_cart', n.item_id + '.' + Date.now(), { ecommerce: { currency: getCurrency(), value: (richItem.price || 0) * richItem.quantity, items: [richItem] } }); } } prev = now; } catch (e) {} } function attachCartObserver() { var root = d.querySelector('.w-commerce-commercecartlist, [data-node-type="commerce-cart-list"]'); if (!root) { // Wait for the cart fly-out to render. var docObs = new MutationObserver(function () { if (d.querySelector('.w-commerce-commercecartlist, [data-node-type="commerce-cart-list"]')) { docObs.disconnect(); attachCartObserver(); } }); docObs.observe(d.documentElement, { childList: true, subtree: true }); return; } prev = readCart(); var obs = new MutationObserver(syncCart); obs.observe(root, { childList: true, subtree: true, attributes: true, characterData: true }); watchCartFlyout(); } // Expose pageType so the enrichment block can set ecomm_pagetype. w.tcPageType = pageType; // ── Bootstrap ──────────────────────────────────────────────────── function boot() { var t = pageType(); log('page type', t); if (t === 'product') fireViewItem(); if (t === 'category') fireViewItemList(); if (t === 'cart') fireViewCart(); if (t === 'checkout') fireBeginCheckout(); if (t === 'order') firePurchase(); attachCartObserver(); } if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', boot); else boot(); })(window, document); </script> <!-- End Traksolv Webflow DataLayer (advanced) --> <!-- 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
Save and Publish
SaveSave Changes → top-right Publish → publish to your live domain
Custom Code only runs on the published site, NOT in the Designer preview. Open your real domain to verify.
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.
Server-side tracking for Webflow, answered
Does server-side tracking work with Webflow?
Yes. TrakSolv works with Webflow via a lightweight loader snippet plus a Webflow-specific dataLayer that fires every GA4 enhanced-ecommerce event. You paste it once in Webflow's custom-code area and conversions start flowing through your own server-side GTM container.
How do I install TrakSolv on Webflow?
Add the TrakSolv loader (head + body) and the Webflow dataLayer snippet to your store's custom-code settings, then save. The full step-by-step is on this page. Most Webflow merchants are live in a few minutes — no developer required.
What can I track server-side on Webflow?
Page views, product views, leads and purchases, 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 Webflow 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 Webflow conversions — start free.
Spin up a first-party server-side GTM container for Webflow and watch your match quality climb. No credit card, live in minutes.
