Back to the blog

Blog

Server-side tagging for a Shopify or WooCommerce store: what fires, what does not, and what to install

Published · 4 min read

A server container does not make a store report purchases. It forwards what the browser sends, from your own hostname instead of Google's, and that is worth doing — ad blockers and Safari between them drop a meaningful share of a shop's conversions. But the purchase has to be sent first, and on the two platforms most shops run, nothing sends it out of the box. This is the article we wish we could have handed every customer on day one.

The seven events, and the one thing they have in common

GA4 defines seven events for a purchase funnel: view_item_list, view_item, add_to_cart, remove_from_cart, begin_checkout, add_payment_info and purchase. Their names are GA4's, not any platform's — the same seven work for Shopify, WooCommerce and a shop you built yourself.

What they share is a mechanism. Each one is a push to the page's dataLayer, by exactly that name, carrying an ecommerce object in GA4's shape. A Tag Manager trigger listens for the name; a GA4 event tag reads the ecommerce object and sends it. Misspell the name, nest the object one level too deep, or push it on the wrong page, and the tag sits there forever without an error. This is what a purchase push looks like:

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
  event: 'purchase',
  ecommerce: {
    transaction_id: 'ORD-10442',
    value: 89.90,
    currency: 'EUR',
    items: [
      { item_id: 'SKU-118', item_name: 'Trail jacket', price: 89.90, quantity: 1 }
    ]
  }
});

The push of ecommerce: null first is not decoration. Without it, the previous event's items can bleed into the next one, and a view_item from three pages ago shows up inside a purchase.

Shopify: the theme cannot see the checkout

A container installed in a Shopify theme loads on the storefront — product pages, collections, the cart. It does not load on the checkout or the thank-you page, which Shopify serves from its own domain and its own templates. So a theme-installed container can send view_item and add_to_cart and will never, under any configuration, send begin_checkout, add_payment_info or purchase.

Those three come from a Custom Pixel. Shopify's pixel sandbox exposes checkout events (checkout_started, payment_info_submitted, checkout_completed) and lets a pixel push to a dataLayer of its own — a small script maps each to the GA4 event name and shape above. The storefront events still come from the theme, so a complete setup has both: a dataLayer app or theme snippet for the browsing half, a Custom Pixel for the checkout half.

One more thing to decide. Shopify's Google & YouTube channel can send GA4 events directly, outside any container. If you connect a GA4 property there AND route the same property through your container, every purchase is counted twice. Pick one path. If the point is first-party collection, the container is the path, and the channel's Analytics connection stays off.

WooCommerce: core pushes nothing

WooCommerce itself writes no dataLayer at all. A plugin does — GTM4WP is the one most people reach for — and its GA4 e-commerce option produces the seven events in the right shape on the right pages: view_item on a product, add_to_cart on the button, begin_checkout on the checkout page, purchase on the order-received page.

The classic WooCommerce fault is a doubled purchase, and it has a specific cause: the order-received page reloads. A visitor refreshes it, returns to it from a payment provider, or the theme redirects through it twice, and the purchase pushes again with the same transaction_id. GA4 deduplicates purchases by transaction_id within a session, which catches most of it, but not a reload the next day. The plugin's "only fire once per order" setting is worth turning on; it keeps the flag server-side, where a reload cannot reset it.

What SignalHost builds for you

Tell the wizard the site is a shop and it adds, to your web container, a Custom Event trigger and a GA4 event tag for each of the seven events — fourteen entities, all named with an SH prefix so they never collide with anything you already have. Each tag sends the ecommerce object straight from the dataLayer, so items, value, currency and transaction_id come through without a variable per field, and each names your measurement ID explicitly rather than inheriting it.

Nothing is installed on your store. The triggers listen; the pushes are the plugin's or the pixel's. Choosing "no online shop" leaves the fourteen entities out and changes nothing else — page views, scroll and the rest of the standard events route through the container either way.

On the server side there is nothing per event. The server container's GA4 tag forwards every event it receives, whatever it is called, so a new event on your site needs no change on ours. The Meta Conversions API and TikTok Events API destinations, if you connect them, map the same seven names themselves; a purchase becomes a Purchase for Meta and a CompletePayment for TikTok, from the same push.

Checking it with one test order

Place a real order in a test mode, or a cheap one you refund, and watch the container page's event list. In sequence you should see view_item, add_to_cart, begin_checkout, add_payment_info and purchase — each once. A missing purchase on Shopify means the Custom Pixel is not installed or not pushing; a missing begin_checkout on WooCommerce usually means the plugin's checkout event is disabled.

If you run Google Ads conversions through the container, purchase is the conversion event, and its value, currency and transaction ID are read from that same ecommerce object. A purchase push with no value records a conversion worth nothing — which is a real number in a report, and the wrong one.

Stop losing data to ad blockers

Ten thousand requests a month, free, for as long as you like. Four minutes to find out whether it helps.