E-commerce stores live and die by their analytics. Knowing which products convert, which campaigns drive revenue, and where shoppers abandon carts is non-negotiable. But the tools most stores rely on, primarily Google Analytics 4 with Enhanced E-Commerce, are built on a foundation of cookies and third-party scripts that crumbles under modern privacy regulations. The result is a growing gap between what actually happens on your store and what your analytics dashboard shows you. This guide walks you through building a complete ecommerce privacy analytics stack that tracks revenue, funnels, and attribution without cookies. Whether you run Shopify or WooCommerce, you will find practical code and configuration to implement today. If you are new to the broader topic, start with our privacy-first analytics for business overview.
The Problem: Why Standard E-Commerce Tracking Breaks Privacy
Google Analytics 4 Enhanced E-Commerce is the default choice for most online stores. It tracks product impressions, add-to-cart events, checkout steps, and purchases. Under the hood, it depends on several mechanisms that conflict directly with privacy regulations.
Cookies are the first issue. GA4 sets a _ga cookie that persists for two years and a _ga_MEASUREMENT_ID cookie for session tracking. These are first-party cookies, but they still require explicit consent under GDPR because they are used for analytics purposes rather than being strictly necessary for the site to function. The moment you add a consent banner, a significant portion of visitors decline. Industry data consistently shows that 30 to 60 percent of European visitors reject analytics cookies. That means your revenue data has a massive blind spot from day one.
Third-party scripts compound the problem. The gtag.js or Google Tag Manager script loads from Google’s servers. Privacy-focused browsers like Safari, Firefox, and Brave increasingly block or limit these requests. Ad blockers, used by roughly 30 percent of desktop users, strip them out entirely. Every blocked script is a purchase that never gets recorded.
Cross-domain tracking is fragile. If your WooCommerce store uses a separate payment gateway domain or your Shopify checkout runs on checkout.shopify.com, GA4 needs cross-domain tracking via link decoration. This adds query parameters that privacy tools strip, breaking the session chain. You see a new user on the thank-you page instead of the original referral source.
The net effect is severe. A store doing one million in monthly revenue might only see 500,000 to 700,000 in tracked revenue through GA4. Attribution data is even worse, with campaign performance reports based on incomplete data leading to poor budget allocation. You need a different approach, one that is cookie-free by design.
Building a Privacy-First E-Commerce Analytics Stack
A privacy-first e-commerce analytics stack replaces cookies and third-party scripts with two core components: a cookie-free analytics platform for client-side tracking and server-side event forwarding for accurate revenue data. The two leading open source options are Matomo and Plausible.
Matomo offers a full e-commerce module with cart tracking, product-level analytics, and funnel visualization. It can run in cookieless mode while still providing detailed reports. It is the stronger choice for stores that need granular product analytics.
Plausible takes a minimalist approach with custom events and revenue goals. It never uses cookies, so consent banners are unnecessary. It is ideal for stores that want clean revenue attribution without managing a complex analytics platform.
Here is the architecture at a high level:
┌─────────────────────────────────────────────────────┐
│ Visitor Browser │
│ │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ Product View │───▶│ Matomo JS (cookieless) │ │
│ │ Add to Cart │ │ OR Plausible script │ │
│ │ Checkout │ └──────────┬───────────────┘ │
│ └─────────────┘ │ │
└────────────────────────────────┼────────────────────┘
│ pageviews + events
▼
┌────────────────────────┐
│ Analytics Server │
│ (Matomo / Plausible) │
└────────────────────────┘
▲
│ server-side events
│ (order complete,
│ revenue, products)
┌────────────────────────┐
│ Store Backend │
│ WooCommerce webhook │
│ or Shopify webhook │
└────────────────────────┘
Client-side tracking captures browsing behavior: which products visitors view, what they add to cart, and how far they get in checkout. Server-side tracking captures confirmed orders directly from your store backend, bypassing browser limitations entirely. Together, they give you a complete picture without touching a single cookie. For a deeper comparison between platforms, see our Matomo vs Plausible vs Fathom analysis.
Matomo E-Commerce Tracking Setup
Matomo has built-in e-commerce analytics that rival GA4 Enhanced E-Commerce. You get product views, cart updates, and order tracking out of the box. Here is how to set it up in cookieless mode.
Enable E-Commerce in Matomo
In your Matomo dashboard, go to Administration, then Websites, then Manage. Edit your site and set the E-commerce toggle to enabled. This activates the E-commerce section in your reporting sidebar where you will see revenue, product performance, and cart abandonment data.
Configure Cookieless Tracking
Add the Matomo tracking code to your site with cookie tracking disabled. This is the critical step that removes the need for consent banners.
<script>
var _paq = window._paq = window._paq || [];
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u = "https://analytics.yourdomain.com/";
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '1']);
var d = document,
g = d.createElement('script'),
s = d.getElementsByTagName('script')[0];
g.async = true;
g.src = u + 'matomo.js';
s.parentNode.insertBefore(g, s);
})();
</script>
The disableCookies call is all it takes. Matomo will use a fingerprint-free approach based on the visitor’s IP address and user agent, hashed daily, to provide visit-level analytics without persistent identifiers.
Track Product Views
On individual product pages, push product view data before the page view is tracked. For WooCommerce, you can hook into the template to output this dynamically.
<script>
// Set e-commerce view for the current product page
_paq.push(['setEcommerceView',
'SKU-1234', // Product SKU
'Organic Cotton Tee', // Product name
['Apparel', 'T-Shirts'], // Product categories
29.99 // Product price
]);
_paq.push(['trackPageView']);
</script>
Track Cart Updates
When a visitor adds or removes items from the cart, update Matomo with the current cart state. This powers cart abandonment reporting.
<script>
// Add each product in the cart
_paq.push(['addEcommerceItem',
'SKU-1234', // SKU
'Organic Cotton Tee', // Name
'Apparel', // Category
29.99, // Unit price
2 // Quantity
]);
_paq.push(['addEcommerceItem',
'SKU-5678',
'Recycled Denim Jeans',
'Apparel',
79.99,
1
]);
// Update cart with the total
_paq.push(['trackEcommerceCartUpdate', 139.97]);
</script>
Track Completed Orders
On the order confirmation page, record the transaction. Include all items and the order total.
<script>
// Add items from the completed order
_paq.push(['addEcommerceItem',
'SKU-1234', 'Organic Cotton Tee', 'Apparel', 29.99, 2
]);
_paq.push(['addEcommerceItem',
'SKU-5678', 'Recycled Denim Jeans', 'Apparel', 79.99, 1
]);
// Track the order
_paq.push(['trackEcommerceOrder',
'ORDER-20260325-001', // Order ID
139.97, // Grand total (revenue)
119.98, // Subtotal
9.99, // Tax
10.00, // Shipping
0 // Discount
]);
</script>
WooCommerce Integration
For WooCommerce specifically, you can output tracking calls dynamically using PHP hooks. Add this to your theme’s functions.php or a custom plugin. For details on building full funnels with Matomo, see our e-commerce funnel tracking with Matomo guide.
// functions.php or custom plugin
add_action('woocommerce_after_single_product', function() {
global $product;
if (!$product) return;
$categories = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'names']);
$category = !empty($categories) ? $categories[0] : '';
printf(
'<script>
_paq.push(["setEcommerceView", "%s", "%s", "%s", %s]);
_paq.push(["trackPageView"]);
</script>',
esc_js($product->get_sku()),
esc_js($product->get_name()),
esc_js($category),
(float) $product->get_price()
);
});
add_action('woocommerce_thankyou', function($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
$items_js = '';
foreach ($order->get_items() as $item) {
$prod = $item->get_product();
$cats = wp_get_post_terms($prod->get_id(), 'product_cat', ['fields' => 'names']);
$cat = !empty($cats) ? $cats[0] : '';
$items_js .= sprintf(
'_paq.push(["addEcommerceItem", "%s", "%s", "%s", %s, %s]);' . "\n",
esc_js($prod->get_sku()),
esc_js($item->get_name()),
esc_js($cat),
(float) $prod->get_total() / max($item->get_quantity(), 1),
(int) $item->get_quantity()
);
}
printf(
'<script>
%s
_paq.push(["trackEcommerceOrder", "%s", %s, %s, %s, %s, false]);
</script>',
$items_js,
esc_js($order->get_order_number()),
(float) $order->get_total(),
(float) $order->get_subtotal(),
(float) $order->get_total_tax(),
(float) $order->get_shipping_total()
);
});
Plausible Revenue Tracking with Custom Events
Plausible takes a fundamentally different approach. Instead of dedicated e-commerce APIs, it uses custom events with revenue properties. This keeps the implementation simple and the script tiny, under 1 KB.
Set Up Revenue Goals
In your Plausible dashboard, go to Site Settings, then Goals, and create a custom event goal named Purchase. Enable the Revenue toggle and set your currency. This tells Plausible to expect monetary values attached to this event. You can also create goals for AddToCart and BeginCheckout to build funnels.
Track Purchases with Revenue
On your order confirmation page, fire the purchase event with the revenue amount and custom properties for product metadata.
<script>
// Requires Plausible script with tagged-events extension
// <script defer data-domain="yourstore.com"
// src="https://plausible.yourdomain.com/js/script.revenue.tagged-events.js">
plausible('Purchase', {
revenue: { currency: 'USD', amount: 139.97 },
props: {
order_id: 'ORDER-20260325-001',
product_category: 'Apparel',
item_count: '3',
payment_method: 'stripe'
}
});
</script>
Track Add to Cart and Checkout Events
For funnel steps leading up to purchase, fire events without revenue data but with useful properties.
<script>
// When a visitor adds an item to cart
plausible('AddToCart', {
props: {
product_name: 'Organic Cotton Tee',
product_category: 'Apparel',
price: '29.99'
}
});
// When a visitor begins checkout
plausible('BeginCheckout', {
props: {
cart_value: '139.97',
item_count: '3'
}
});
</script>
Pageview-Based Funnels
If you prefer not to wire up custom events for every action, Plausible can build funnels purely from pageview paths. Define funnel steps as URL patterns: /products/* for product views, /cart for the cart page, /checkout for the checkout page, and /order-confirmation for completed purchases. This approach requires zero custom JavaScript and works immediately.
Server-Side Tracking for Accurate Revenue Data
Client-side tracking, no matter how well implemented, will always miss some orders. Visitors with JavaScript disabled, aggressive browser extensions, or network issues may complete a purchase that the analytics script never sees. For accurate revenue data, you must supplement client-side tracking with server-side event forwarding.
The concept is simple. When your store backend confirms an order, it sends the transaction data directly to your analytics platform’s API. This happens server-to-server, completely bypassing the browser. No cookies, no scripts, no blockers.
WooCommerce Server-Side Tracking with Matomo
Use the WooCommerce woocommerce_order_status_completed hook to fire a server-side event to Matomo’s HTTP Tracking API.
// In a custom plugin or functions.php
add_action('woocommerce_order_status_completed', function($order_id) {
$order = wc_get_order($order_id);
if (!$order) return;
// Prevent duplicate tracking
if ($order->get_meta('_matomo_tracked')) return;
$matomo_url = 'https://analytics.yourdomain.com/matomo.php';
$site_id = 1;
$auth_token = 'your_matomo_auth_token'; // Keep in wp-config.php
// Build item parameters
$items = [];
foreach ($order->get_items() as $item) {
$product = $item->get_product();
$cats = wp_get_post_terms(
$product->get_id(), 'product_cat', ['fields' => 'names']
);
$items[] = [
esc_attr($product->get_sku()),
esc_attr($item->get_name()),
!empty($cats) ? esc_attr($cats[0]) : '',
(float) ($item->get_total() / max($item->get_quantity(), 1)),
(int) $item->get_quantity()
];
}
$params = [
'idsite' => $site_id,
'rec' => 1,
'url' => home_url('/order-received/' . $order_id),
'action_name' => 'Order Completed',
'idgoal' => 0, // E-commerce tracking
'ec_id' => $order->get_order_number(),
'revenue' => (float) $order->get_total(),
'ec_st' => (float) $order->get_subtotal(),
'ec_tx' => (float) $order->get_total_tax(),
'ec_sh' => (float) $order->get_shipping_total(),
'ec_items' => json_encode($items),
'token_auth' => $auth_token,
'cip' => $order->get_customer_ip_address(),
];
$response = wp_remote_get(
$matomo_url . '?' . http_build_query($params),
['timeout' => 5]
);
if (!is_wp_error($response)) {
$order->update_meta_data('_matomo_tracked', 'yes');
$order->save();
}
});
WooCommerce Server-Side Tracking with Plausible
Plausible offers an Events API that accepts server-side requests. The endpoint expects a simple POST request.
add_action('woocommerce_order_status_completed', function($order_id) {
$order = wc_get_order($order_id);
if (!$order || $order->get_meta('_plausible_tracked')) return;
$payload = json_encode([
'name' => 'Purchase',
'domain' => 'yourstore.com',
'url' => home_url('/order-received/' . $order_id),
'props' => [
'order_id' => $order->get_order_number(),
'product_category' => 'mixed',
'item_count' => (string) $order->get_item_count(),
'payment_method' => $order->get_payment_method(),
],
'revenue' => [
'currency' => $order->get_currency(),
'amount' => (float) $order->get_total(),
],
]);
$response = wp_remote_post(
'https://plausible.yourdomain.com/api/event',
[
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => $order->get_customer_user_agent() ?: 'Server',
'X-Forwarded-For' => $order->get_customer_ip_address(),
],
'body' => $payload,
'timeout' => 5,
]
);
if (!is_wp_error($response)) {
$order->update_meta_data('_plausible_tracked', 'yes');
$order->save();
}
});
The X-Forwarded-For header is important. It lets Plausible attribute the event to the correct geographic location. Without it, all server-side events would appear to come from your server’s IP address. For more on self-hosting these analytics platforms, see our self-hosted analytics complete guide.
Funnel Analysis Without Cookies
Funnel analysis tells you where shoppers drop off in the buying process. In a traditional cookie-based setup, the analytics tool chains sessions together using a persistent identifier. Without cookies, you need a different approach.
Defining Funnel Steps
A standard e-commerce funnel has four stages:
- Product View — visitor lands on a product page
- Add to Cart — visitor adds an item to the shopping cart
- Checkout — visitor enters the checkout flow
- Purchase — visitor completes the order and sees the thank-you page
Matomo Funnels Plugin
Matomo’s Funnels plugin (available in Matomo On-Premise and Matomo Cloud) lets you define funnel steps based on either page URLs or custom events. Even in cookieless mode, Matomo can construct per-visit funnels because it uses a config_id hash derived from the visitor’s IP and user agent to group pageviews within a single visit.
To create a funnel in Matomo, go to Goals, create a new goal for Purchase with a pattern matching your thank-you page URL, then edit the goal and define funnel steps. Map each step to a URL pattern or to the e-commerce events you are already tracking. Matomo will then show you a visual funnel with drop-off percentages at each step.
For stores with many products, URL-pattern matching works well. Set step one as any URL matching /product/, step two as /cart/, step three as /checkout/, and the goal as /order-received/. Matomo will calculate conversion rates between steps automatically.
Plausible Funnel Feature
Plausible added funnels as a first-class feature. You define funnel steps using the goals you already created, either pageview goals or custom event goals. Navigate to your site settings, select Funnels, and build a new funnel with steps mapped to your goals: Product View (pageview matching /products/*), AddToCart (custom event), BeginCheckout (custom event), and Purchase (custom event).
Because Plausible never uses cookies, it tracks funnels within a single session using its internal session model based on the visitor’s IP hash and user agent. This means it can show you how many visitors progress through each step within a single browsing session, which is exactly what you need for e-commerce funnel analysis.
UTM Attribution in Cookie-Free Setups
Knowing your revenue totals is only half the picture. You also need to know which marketing campaigns drive that revenue. In a cookie-based world, Google Analytics stores UTM parameters in a cookie and associates them with future conversions. Without cookies, you need to carry UTM data through the session yourself.
Both Matomo and Plausible automatically parse UTM parameters from the current page URL and associate them with the pageview. The challenge arises when the conversion happens on a later page, such as the thank-you page, which no longer has UTM parameters in its URL.
SessionStorage UTM Preservation
The solution is lightweight and privacy-friendly: store UTM values in sessionStorage when a visitor first arrives, then include them in your conversion events. SessionStorage is cleared when the browser tab closes, making it far less invasive than cookies. For a comprehensive deep-dive into this approach, see our guide on UTM parameters without cookies.
<script>
(function() {
// On first page load, capture UTM params into sessionStorage
var params = new URLSearchParams(window.location.search);
var utmKeys = ['utm_source', 'utm_medium', 'utm_campaign',
'utm_term', 'utm_content'];
utmKeys.forEach(function(key) {
var value = params.get(key);
if (value) {
sessionStorage.setItem(key, value);
}
});
})();
// When tracking a purchase, retrieve stored UTMs
function getStoredUTMs() {
var utms = {};
['utm_source', 'utm_medium', 'utm_campaign',
'utm_term', 'utm_content'].forEach(function(key) {
var val = sessionStorage.getItem(key);
if (val) utms[key] = val;
});
return utms;
}
// Example: attach UTMs to a Plausible purchase event
function trackPurchase(orderId, total) {
var utms = getStoredUTMs();
plausible('Purchase', {
revenue: { currency: 'USD', amount: total },
props: Object.assign({
order_id: orderId
}, utms)
});
}
// Example: attach UTMs to a Matomo order
function trackMatomoOrder(orderId, total, subtotal, tax, shipping) {
var utms = getStoredUTMs();
if (utms.utm_campaign) {
_paq.push(['setCampaignNameKey', utms.utm_campaign]);
}
if (utms.utm_source) {
_paq.push(['setCampaignKeywordKey', utms.utm_source]);
}
_paq.push(['trackEcommerceOrder', orderId, total,
subtotal, tax, shipping, false]);
}
</script>
This approach preserves first-touch attribution within a single browsing session. If a visitor arrives via a Facebook ad, browses several products, and completes a purchase, the UTM parameters from the original ad click will be attached to the order event. The data lives entirely in the browser’s memory and is never written to disk or shared with third parties.
Shopify-Specific Setup
Shopify presents unique challenges because you have limited control over the checkout process. The checkout pages run on Shopify’s infrastructure, and custom scripts are restricted unless you are on Shopify Plus.
Adding Matomo to Shopify
You have two options for adding the Matomo tracking script to Shopify. The first is to edit your theme’s theme.liquid file and paste the Matomo JavaScript snippet directly before the closing </head> tag. This is the most reliable method and works on all Shopify plans. The second option is to use Google Tag Manager if it is already installed on your store. Add the Matomo tracking code as a Custom HTML tag that fires on all pages.
For product view tracking, Shopify exposes product data in a JavaScript object on product pages. You can use it to populate Matomo’s e-commerce view.
<script>
// In theme.liquid or a theme section for product pages
{% if template contains 'product' %}
_paq.push(['setEcommerceView',
'{{ product.selected_or_first_available_variant.sku | escape }}',
'{{ product.title | escape }}',
'{{ product.type | escape }}',
{{ product.price | money_without_currency | replace: ',', '' }}
]);
{% endif %}
_paq.push(['trackPageView']);
</script>
Adding Plausible to Shopify
Plausible is even simpler. Go to your Shopify admin, navigate to Online Store then Themes, click Customize, then open Theme Settings. Look for the Additional Scripts section in the header. Paste the Plausible script tag.
<script defer data-domain="yourstore.com"
src="https://plausible.yourdomain.com/js/script.revenue.tagged-events.js">
</script>
Shopify Checkout Limitations
The biggest limitation on standard Shopify plans is that you cannot add custom JavaScript to checkout pages or the order confirmation page. This means client-side purchase tracking will not work unless you are on Shopify Plus, which allows checkout extensibility.
The workaround is server-side tracking via Shopify webhooks. In your Shopify admin, go to Settings, then Notifications, then Webhooks. Create a webhook for the orders/paid event pointing to your own server endpoint. Your server then forwards the order data to Matomo or Plausible using the HTTP APIs described in the server-side tracking section above.
For add-to-cart tracking on non-checkout pages, you can intercept Shopify’s AJAX cart API by listening for fetch or XMLHttpRequest calls to /cart/add.js.
<script>
// Intercept Shopify add-to-cart AJAX calls
var origFetch = window.fetch;
window.fetch = function(url, opts) {
if (typeof url === 'string' && url.includes('/cart/add.js')) {
// Parse the form data or JSON body for product info
origFetch.apply(this, arguments).then(function(response) {
return response.clone().json();
}).then(function(data) {
// Plausible example
plausible('AddToCart', {
props: {
product_name: data.title || 'Unknown',
price: (data.price / 100).toFixed(2),
variant_id: String(data.variant_id || '')
}
});
// Matomo example
_paq.push(['addEcommerceItem',
String(data.sku || data.variant_id),
data.title || 'Unknown',
data.product_type || '',
data.price / 100,
data.quantity || 1
]);
_paq.push(['trackEcommerceCartUpdate', data.price / 100]);
});
}
return origFetch.apply(this, arguments);
};
</script>
Comparing the Stacks: Matomo vs Plausible for E-Commerce
Choosing between Matomo and Plausible depends on your store’s complexity, team size, and budget. Here is a side-by-side comparison focused on e-commerce use cases.
| Feature | Matomo | Plausible |
|---|---|---|
| Dedicated e-commerce module | Yes, built-in with product-level reports | No, uses custom events with revenue |
| Cart tracking | Yes, trackEcommerceCartUpdate API | Manual via custom events |
| Product-level analytics | Yes, SKU-level reports, product views, conversion rates | Limited, via custom properties only |
| Funnel visualization | Yes, via Funnels plugin (free in On-Premise) | Yes, built-in funnel feature |
| Revenue tracking | Yes, order totals, subtotals, tax, shipping | Yes, total revenue per event |
| Server-side API | HTTP Tracking API, full e-commerce support | Events API with revenue support |
| Cookieless mode | Yes, via disableCookies config | Yes, always cookie-free |
| Script size | ~22 KB (gzipped) | ~1 KB (gzipped) |
| Ease of setup | Moderate, more configuration required | Easy, minimal setup |
| Self-hosted option | Yes, free (Matomo On-Premise) | Yes, free (Plausible CE) |
| Cloud option | From 23 EUR/month | From 9 USD/month |
| Best for | Stores needing detailed product analytics | Stores wanting simplicity and speed |
If you run a WooCommerce store with hundreds of products and need to analyze performance at the SKU level, Matomo is the stronger choice. Its e-commerce module provides the granularity you need without building custom reporting. If you run a smaller catalog or a Shopify store and want fast setup with minimal maintenance, Plausible delivers accurate revenue data with far less complexity.
Both platforms can be self-hosted, which keeps all data under your control and eliminates recurring SaaS costs beyond server expenses. For stores processing sensitive customer data, self-hosting is often a compliance requirement rather than a preference.
Bottom Line
Standard e-commerce analytics stacks are broken for privacy. Cookie consent banners, ad blockers, and browser restrictions create blind spots that can hide 30 to 60 percent of your revenue data. The fix is not to fight these protections but to build around them.
A privacy-first ecommerce privacy analytics stack built on Matomo or Plausible gives you complete revenue tracking without cookies. Client-side scripts capture browsing behavior and funnel progression. Server-side webhooks ensure every confirmed order is recorded regardless of what happens in the browser. SessionStorage carries UTM attribution through the session without persistent tracking. Together, these layers give you more accurate data than a consent-gated GA4 setup ever could.
Start with the server-side tracking. It is the single highest-impact change you can make because it guarantees that every completed order appears in your analytics. Then layer on client-side event tracking for cart and funnel analysis. The code examples in this guide work today with current versions of Matomo, Plausible, WooCommerce, and Shopify. Pick the platform that matches your store’s complexity and get it running this week.
