Install the AxiTrace loader through a Google Tag Manager Custom HTML tag and map your existing GA4 ecommerce dataLayer to AxiTrace events - no shop code changes.

Install with Google Tag Manager

If your shop already runs Google Tag Manager and already pushes a GA4 ecommerce dataLayer, you can go live with AxiTrace without touching a single line of shop code. One Custom HTML tag loads the AxiTrace loader, one switch in Workspace settings tells it to read your dataLayer, and your existing view_item, add_to_cart, begin_checkout and purchase pushes become AxiTrace events automatically. The full reference for reading the dataLayer is GA4 dataLayer Auto-Tracking.

Who this page is for

You run a custom shop, a headless storefront, or any site that has no AxiTrace plugin. Your two documented options so far were to hand-write JavaScript (JavaScript SDK) or to send events from your own backend (HTTP API). This page is the third option: install through Google Tag Manager, which is what most shops in this position already have.

If you run WooCommerce, Shopify, Magento, Shopware or BigCommerce, do not use this page. Install the plugin for your platform instead: it captures the order server-side, which is strictly better than anything a browser can do. See the platform integration guides.

The one prerequisite: a GA4 ecommerce dataLayer

Everything on this page depends on a single question: does your site already push GA4 ecommerce events, or the classic Google Analytics ecommerce transaction, into a dataLayer? If it does, the install below takes about ten minutes. If it does not, someone still has to write those pushes into your shop's templates, and Google Tag Manager does not remove that work, it moves it. Be clear about which of the two situations you are in before you start.

Check your own dataLayer in 30 seconds

Open a product page on your site, open the browser devtools console, and paste this. The first part prints what the page has pushed already; the second part prints every new push live, so you can click Add to cart and watch what happens. Then do the same on the order confirmation page after a test order: on many shops that is the only page where the order data appears.

// 1. What this page has already pushed.
(function () {
  var dl = window.dataLayer;
  if (!Array.isArray(dl)) {
    console.warn('No dataLayer array on this page. Nothing to map.');
    return;
  }
  console.log('dataLayer entries:', dl.length);
  dl.forEach(function (entry, i) {
    if (entry && entry.transactionId) {
      console.log(i, 'classic ecommerce transaction', entry);
    } else if (entry && entry.event) {
      console.log(i, entry.event, entry.ecommerce || '(no ecommerce object)');
    }
  });
})();

// 2. Print every future push. Now click "Add to cart" and watch the console.
(function () {
  var dl = window.dataLayer;
  if (!Array.isArray(dl)) { return; }
  var originalPush = dl.push;
  dl.push = function () {
    try { console.log('PUSH', arguments[0]); } catch (e) {}
    return originalPush.apply(dl, arguments);
  };
  console.log('Watching dataLayer pushes. Reload the page to stop.');
})();

Reading the result

What you see What it means
Entries with event: "view_item", "add_to_cart", "begin_checkout", "purchase", each with an ecommerce object You have a GA4 ecommerce dataLayer. Go to Path 1 and you are done in ten minutes.
A dataLayer exists, but the only entries are gtm.js, gtm.dom, gtm.load and similar That is Google Tag Manager talking to itself. There is no ecommerce data in it. Check the order confirmation page too (the classic ecommerce row). If there is none there either, add the pushes to your shop's templates, or use Path 2.
Entries with an ecommerce object but names like productClick or eec.purchase That is the older Universal Analytics enhanced ecommerce shape. AxiTrace translates its purchase, checkout, add and detail blocks to the GA4 events; other Universal Analytics actions are not read. See what is read.
On the order confirmation page, a classic ecommerce transaction entry with transactionId, transactionTotal and transactionProducts (often with no event, so it only shows up in this check) That is the classic Google Analytics ecommerce format. AxiTrace reads it as a purchase as it is, with no variables to map, so Path 1 works and the orders arrive. Set your workspace's default currency in Workspace settings, because this format carries none. See classic ecommerce transactions.
No dataLayer array on this page Nothing to map. Someone has to write the pushes, or use Path 2.
Be honest with yourself about this step

A tag manager is a place to put tags. It is not a source of data. If your product pages do not publish the SKU, the price and the currency somewhere a script can read them, no configuration inside GTM will invent them. The work of emitting that data is the same work whether it ends up in a dataLayer, in a plugin, or in a hand-written SDK call - the only thing that changes is who does it and where it lives.

1

Create the Custom HTML tag

In your web Google Tag Manager container, go to Tags → New, choose Custom HTML, and paste the snippet below. Replace axi.yourdomain.com with your own tracking domain and pk_live_... with your workspace Public Key (both are shown in the AxiTrace admin panel).

<script src="https://axi.yourdomain.com/axitrack.js"></script>
<script>
  Axitrace.init({
    publicKey: 'pk_live_...',
    apiUrl: 'https://axi.yourdomain.com'
  });
</script>

The options, and what the Workspace setting does instead

Option Required Default What it does
publicKey Yes none Your workspace Public Key. Starts with pk_live_. It is a public value and is meant to sit in page source.
apiUrl Yes none Your first-party tracking domain, the one you connected and verified in AxiTrace. See Domain Configuration.
autoTrackDataLayer No false Leave it out. Reading the dataLayer is switched on in Workspace settings (see below). A page that passes true keeps it on regardless of the Workspace switch, so the switch can no longer turn it off. With auto-tracking off, the SDK tracks page views, form submissions and engagement; ecommerce events then need either the dataLayer auto-tracking or explicit tracking calls.
dataLayerName No 'dataLayer' Only needed if your GTM container was installed with a renamed dataLayer, that is if your GTM snippet ends with something other than 'dataLayer' as its fourth argument. The Workspace setting has a field for the name too; a non-default name set here wins.

The JavaScript SDK page documents many more initialization options (consent gating, cookie domain, session length, pixel bridging). They all work here too. The two required above are the only ones this install method needs.

Switch on dataLayer auto-tracking

In the AxiTrace admin panel, open Workspace settings, the Data collection section, and turn on GA4 dataLayer auto-tracking. You may find it already on: the AI shop check on the Manual connection page looks for a GA4 ecommerce dataLayer on your shop and sets this switch for you. The SDK picks the setting up by itself, so the tag above never has to change. See the Workspace setting for the details.

2

Fire it on Initialization, All Pages

Give the tag the built-in Initialization - All Pages trigger, not All Pages. Initialization triggers fire before every other tag on the page, which is what you want for a loader: the SDK has to be listening before your shop pushes view_item, or the first event of the page is the one you lose.

Leave Tag firing options at Once per page and leave Support document.write unchecked. Name the tag something like AxiTrace Loader and save.

Pushes that happened before the tag ran are not lost

The SDK reads the entries already sitting in the dataLayer when it starts, and then watches for new ones. A shop that prints its view_item push high in the <head>, before GTM has finished loading, is still tracked correctly.

3

Publish and check the order of your pixels

Submit the container. If your Meta Pixel and TikTok Pixel are also managed by this GTM container, give them the same Initialization - All Pages trigger so they are present as early as possible. AxiTrace calls fbq() and ttq() with its own event ID so the browser and server copies of each event deduplicate, and since SDK 0.7.6 those calls are queued and replayed for up to 30 seconds if the pixel is not loaded yet. This pairing on a shared event ID is done by Meta, TikTok, Pinterest and Microsoft Ads. GA4 does not deduplicate at all: if your own GA4 tag already sends these events, keep Forward browser-bridged dataLayer events off on your AxiTrace GA4 connection, or GA4 counts them twice. A pixel that arrives late is still bridged - loading it early just makes the browser event fire sooner.

Install your Meta and TikTok pixels init-only: no fbq('track', 'PageView') and no ttq.page(). AxiTrace fires those itself, with the shared event ID that makes deduplication work. Leaving the manual calls in place double-counts every page view. See Pixel Deduplication.

What gets mapped, and what does not

With dataLayer auto-tracking on, these GA4 event names become AxiTrace events. Nothing else on the page has to change: the SDK reads the ecommerce object of each push and fills in the AxiTrace event properties from it.

GA4 dataLayer event AxiTrace event
view_itemproduct.view
view_item_listview_item_list
select_itemselect_item
add_to_cartproduct.addToCart
remove_from_cartremove_from_cart
begin_checkoutbegin_checkout
add_shipping_infoadd_shipping_info
add_payment_infoadd_payment_info
purchasepurchase
searchsearch
generate_leadform.submit

purchase is AxiTrace's accepted alias for transaction.charge, which is the name you will see in the Events Reference and in your error log. They are the same event.

view_item_list, select_item, remove_from_cart and add_shipping_info were added to this list on 19 September 2026. Before that they were ignored, which meant a shop with a complete GA4 ecommerce dataLayer got seven of the eleven events it was already publishing. If you built a workaround for that - a backend call, or a custom event - you can remove it: keep only one source per event, or the same action is counted twice.

Not covered yet

These GA4 events are not mapped. They are ignored, silently and harmlessly - no error, no partial event:

  • view_cart
  • refund

Do not try to send these from a GTM tag by hand. The browser endpoint has no handler for them, so the call is answered 400 unsupported event and the attempt is recorded in your workspace's error log. Unlike the four above, these two have no server-side endpoint either, so there is nothing a backend integration can do about them yet.

A reloaded confirmation page is not a second sale

purchase is deduplicated on transaction_id, per workspace, within 30 days: two pushes carrying the same transaction_id are one order, and the repeat is dropped. A buyer who reloads the order confirmation page, or a tag that fires twice, does not double-count revenue in AxiTrace. Your ad platforms receive only the first purchase with a given order id, whatever source it came from.

This depends on transaction_id actually being present in the push. A purchase event without one cannot be recognised as a repeat of anything. Check for it in the console output from the dataLayer check above before you trust this.

AxiTrace remembers a purchase for 30 days, so this holds for a confirmation page re-opened from browser history days later, not only for an immediate reload. Ordinary browser events such as product.view keep a much shorter window on purpose: a visitor who opens the same product page again tomorrow really is a new view.

Path 2: no dataLayer, fire events from GTM tags

If the check above found no ecommerce data, you can still build events inside GTM out of triggers and variables. Keep the loader tag from Path 1 without autoTrackDataLayer, keep GA4 dataLayer auto-tracking off in Workspace settings (the shop check leaves it off when it finds no GA4 ecommerce dataLayer), and add one Custom HTML tag per event. Never combine the two: events sent by hand and the same events read from the dataLayer are counted twice.

Values scraped from the page break on the next redesign

A DOM Element variable is a CSS selector pointed at your own markup. The day a theme update renames a class, moves the price into a different element, or changes 49.99 USD to USD 49.99, the variable returns the wrong value or nothing at all - and nothing warns you. Your tag keeps firing, your conversions keep arriving, and the amounts are quietly wrong.

Treat Path 2 as a bridge, not a destination. A real dataLayer, a plugin, or a server-side integration all survive a redesign; a selector does not.

1

Create the variables

Under Variables → User-Defined Variables, create one DOM Element variable per value you need. For an add-to-cart event that is typically the SKU, the name and the price:

Variable name Type Selection method Example selector
DOM - Product SKU DOM Element CSS Selector [data-product-sku], attribute data-product-sku
DOM - Product Name DOM Element CSS Selector h1.product-title
DOM - Product Price DOM Element CSS Selector [data-price], attribute data-price

Prefer a data- attribute over element text wherever your templates offer one. An attribute holds a clean machine value (49.99); element text holds whatever the page displays ($49.99, or 49,99 EUR), and you would have to clean it up in JavaScript before sending it.

2

Create the trigger

Under Triggers → New, choose Click - All Elements, set it to fire on Some Clicks, and add the condition Click Classes contains add-to-cart (substitute whatever class or ID your own button carries). Name it Click - Add to Cart.

3

Create the tag

Add a Custom HTML tag with the snippet below and give it the trigger from Sub-step 2. The double-brace names are GTM variable references: GTM substitutes the value at fire time.

<script>
  (function () {
    // The loader tag fires on Initialization, so the SDK is normally ready by
    // the time a visitor clicks. Guard anyway: a click in the first few hundred
    // milliseconds of a slow page should be skipped, not throw.
    if (!window.Axitrace || !window.Axitrace._instance) { return; }

    var price = parseFloat(String({{DOM - Product Price}}).replace(',', '.'));

    window.Axitrace._instance.track('product.addToCart', {
      content_id: {{DOM - Product SKU}},
      content_name: {{DOM - Product Name}},
      content_type: 'product',
      sku: {{DOM - Product SKU}},
      name: {{DOM - Product Name}},
      price: price,
      quantity: 1,
      currency: 'EUR',
      value: price
    });
  })();
</script>

Repeat the pattern for the other events you need, using the names from the mapping table and the property shapes documented under E-commerce Tracking and in the Events Reference. Hard-code the currency only if your shop sells in one; otherwise it needs its own variable.

What you still do not get from a browser-only install

This is worth reading before you decide the install is finished. A GTM install is a browser install, and a browser install has three limits that no amount of GTM configuration removes.

1. The purchase exists only if the browser reaches the confirmation page

A purchase event is a tag firing on a page. If that page never loads, the event never happens and the order is simply absent from AxiTrace and from every ad platform you forward to. That covers more real orders than most people expect:

  • the payment gateway takes the buyer to its own domain and the return redirect fails, times out, or lands on an error page;
  • the buyer closes the tab on the bank's 3-D Secure screen after the money has already moved;
  • the order is placed by phone, by email, in a physical shop, or entered by your staff in the back office;
  • the buyer's connection drops between the payment confirmation and your thank-you page.

Each of those is a paid conversion your ad platforms will never learn about, and they optimise on what they learn about.

2. Match keys are only as good as what the page exposes

Meta and TikTok score every conversion on how many buyer identifiers arrive with it: email, phone, first name, last name, city, postcode and country. That score is Event Match Quality, and it directly affects how well their algorithms can attribute and optimise. A typical GA4 purchase push carries the order value, the currency, the transaction id and the items - and nothing at all about the buyer, because GA4 never needed it.

What the page does not print, the browser cannot send. Your Event Match Quality is capped at exactly what your confirmation page happens to expose, which is usually far below what your order record already contains.

You can raise that cap by adding GA4 user_data to your pushes: plain-text email, phone_number and an address with first name, last name, city, region, postcode and country. AxiTrace reads it and hashes it itself before sending. Already hashed values and sha256_* keys cannot be used. See customer details from user_data.

3. There is no retry

The browser gets one attempt. If the request is blocked by an extension, cut off by a closing tab, or lost to a dropped connection, that conversion is gone and nothing notices. A server that owns the order can retry until the event is accepted, and AxiTrace's server-side path does exactly that.

The recommended setup is both: browser for the funnel, server for the purchase

Keep this GTM install for the upper funnel. view_item, add_to_cart and begin_checkout are high-volume, they need the visitor's cookies and ad click IDs, and the browser is genuinely the best place to capture them.

Then send the purchase from your backend, from the same code path that already marks the order as paid, using the HTTP API. That copy is authoritative: it exists even when no browser ever reached a confirmation page, it carries the email, phone and address you already hold, and it is retried until it is accepted.

When both paths cover the same order, give the two copies the same event ID: set the HTTP API's eventSalt to a value derived from your order ID, and keep it stable across retries. AxiTrace's 30-day purchase window covers both paths, so the second copy is recognised as the same order rather than counted as a second sale. This is exactly how the platform plugins run a browser pixel and a server-side webhook side by side.

Verify the install

1

GTM Preview

Click Preview in GTM and browse your own shop: a product page, an add to cart, and a test order if you can place one. In the Tag Assistant panel, confirm that your AxiTrace Loader tag appears under Tags Fired on the very first Initialization event of every page, and that it fires once, not repeatedly.

2

The network tab

With devtools open, filter network requests by your tracking domain. You should see axitrack.js load once, then one request per event. Each should return 200.

A 200 is not proof the event was stored

AxiTrace answers 200 to requests it deliberately drops, so that bot detection never leaks back to the client. A headless browser, an automated test runner, or a known crawler gets a clean 200 and no stored event. Always confirm in the admin panel, never in the network tab alone. See Testing with curl (Always 200).

3

The AxiTrace admin panel

Open your workspace and check, in this order:

  • The dashboard. Page views appear within seconds. Conversion events (product.addToCart, begin_checkout, transaction.charge) are held in the queue for 10 seconds on purpose, so the visitor's profile can pick up fbp and fbc first. Wait 15 seconds before deciding an event is missing.
  • Event feed → Errors. This is where a rejected event explains itself. A validation failure, a wrong public key, an origin that is not allowed - each one is recorded here with a reason. An empty Errors tab and an empty Overview together mean the request never arrived at all.
  • The Orders list (Orders & journeys). A test order should appear with the right amount, currency and transaction id. This is also where you see whether your purchase push carried the buyer details, or only the order total.

Troubleshooting

The loader tag fires, but no events arrive

Check that GA4 dataLayer auto-tracking is on in Workspace settings, in the Data collection section. With it off, the SDK tracks page views, form submissions and engagement, and maps nothing from the dataLayer. Then re-run the dataLayer check on a live page: if the pushes use event names that are not in the mapping table, nothing matches and nothing is sent.

Page views work, ecommerce events do not

That is the signature of a loader that is installed correctly and a dataLayer the SDK cannot read. The most common causes are a renamed dataLayer (enter the name from your GTM snippet's fourth argument in the dataLayer name field of the Workspace setting), and pushes whose ecommerce object is missing or empty. Watch a real push with part 2 of the console snippet and compare it against the GA4 shape.

Every event is counted twice

Three things cause this. The loader tag has All Pages as well as Initialization - All Pages, so it runs twice. Or your Meta and TikTok pixels still make their own fbq('track', 'PageView') and ttq.page() calls, which AxiTrace also fires with a shared event ID - remove the manual ones. Or your shop pushes the same GA4 event twice, which the console watcher will show you directly.

Events are missing for visitors who came from Facebook or Instagram

The Facebook in-app browser closes aggressively, and a tag that runs late can be cut off before its request leaves. GTM always loads asynchronously, so this install method is a little more exposed to it than a synchronous SDK tag in the <head>. If a meaningful share of your paid traffic comes from Meta placements, read Facebook In-App Browser and consider loading the SDK directly in your template instead of through GTM.

Nothing is sent at all, and the console mentions consent

If your workspace has "Respect cookie consent" enabled, or your GTM container blocks the tag until a consent state is granted, AxiTrace stays silent until marketing consent arrives - no cookie, no request. That is working as intended. See Cookie Consent for how to wire your consent tool to it.

A note on server-side GTM

Nothing on this page needs server-side GTM. Your AxiTrace tracking domain is already a first-party, server-side endpoint: events from the browser go to your own domain and are forwarded to Meta, TikTok, Google Ads and GA4 from AxiTrace's servers, not from the visitor's browser. That is the benefit people usually buy sGTM for, and you have it the moment the loader above is live.

Separately, AxiTrace can host a managed Google server-side GTM container for you on Business and Custom plans, on that same domain, with no Cloud Run bill of your own. It is useful if you want your Google tags to run server-side too, or want one place to inspect and reshape outbound tag traffic. It is not a prerequisite for anything here, and it does not improve AxiTrace's own tracking. See Server-Side GTM Hosting if that is what you are after.

Need Help?

If you have any questions or need assistance:

  • Email us at info@axitrace.com - we typically reply within one business day.
  • See the JavaScript SDK reference for every option the loader accepts.
  • See the HTTP API reference to add the server-side purchase described above.