How to Track Post Purchase Upsells

Learn how to update your tracking to ensure maximum conversion tracking coverage for your upsells in the native Shopify checkout.

Overview

Follow this guide to learn how to track post purchase upsells. If you have an upsell app such as Rebuy, CartHook, or Zipify installed on your Shopify Store you have the ability to offer additional products after the checkout has completed. This post-purchase page is presented to your customer after the payment is processed before the thank you page.

How Upsell Funnels Work:

If a customer purchases the upsell, a new event called dl_upsell_purchase will fire. This happens before the final thank you page loads. The purchase information sent on this event refers only to the upsell products (think of it as a completely separate purchase). Next, when your customer reaches the final thank you page we fire the dl_purchase event. If you user never reaches the thank you page we've got you covered with your server-side purchase event triggered by the order being created in Shopify.

📘

Using Our Latest Integration for the Checkout Script via Customer Events:

If you are not using our latest integration for the checkout script via customer events, we recommend that you upgrade following our guide because this will ensure your post-purchase tracking is fully up to date.

What's New?

In December 2023, we updated our approach to tracking on the upsell platform. Previously, we triggered the dl_purchase event from the upsell page and then adjusted to prevent it from firing on the thank you page. We are now transitioning to a new approach where the dl_purchase event will be fired from the thank you page, aligning with standard setups. However, we will continue to enable capturing of upsell purchase events from the post-purchase page.

Why Did we Change Our Approach?

  • Made the setup easier - The new recommended setup has less tag customization and no manual updates needed to your order status scripts.
  • Allows new and returning users to be captured for your purchase event when the user navigates to the thank you page. These values can impact the way you pay on affiliate platforms! Also, allows you to target new vs returning users on your advertising platform. With the previous setup, this data would never be captured.
  • The previous advanced setup is not necessary with so many destinations available for server-side setup.
  • No longer need to be concerned with the 500ms processing time on the post-purchase page.
  • Makes future updates to your Elevar Tracking much easier!

How to Track Post Purchase Upsells

Access Shopify Settings:

  • Begin on your Shopify homepage and use the left-hand navigation menu to click on the "Settings" tab or gear icon located in the lower left-hand corner of the page.
    • (See Figure 1)

Step 1 screenshot

_Figure 1_

Access Checkout Settings:

  • Once you have accessed your Shopify settings, use the left-hand navigational menu to click on the "Checkout" tab.
    • (See Figure 2)

Step 2 screenshot

_Figure 2_

Locate Post-Purchase Page Settings:

  • Once you have accessed the Checkout settings in Shopify, navigate to the section of the page titled "Post-purchase page".
    • (See Figure 3)

Step 3 screenshot

_Figure 3_

Add Code:

  • Copy the script below and add it in the "Additional Scripts" box of your Shopify Post-purchase page checkout settings.
    • Ensure that you update the code with your GA4 ID. Follow this guide to learn how to locate your GA4 ID.
    • Ensure that you update the code with your and Meta (Facebook) ID. Follow this guide to learn how to locate your Meta (Facebook) ID.
<!-- Begin GTM-less Upsell Tracking -->
<script>
// CONFIGURATION - Replace with your actual tracking IDs
const TRACKING_CONFIG = {
    ga4_measurement_id: 'XXXXX', // Replace with GA4 Measurement ID
    facebook_pixel_id: 'XXXXX', // Replace with Facebook Pixel ID
    enable_ga4: true, // Set to false to disable GA4 tracking
    enable_facebook: true // Set to false to disable Facebook tracking
};

var upsellCount = 0;

(function() {
    
    // EVENT HOOKS -----------------------------------------------------------
    // Only listen for upsell events - no initial purchase tracking
    Shopify.on('CheckoutAmended', function (newOrder, initialOrder) {
        onCheckoutAmended(newOrder, initialOrder, window.Shopify);
    });
    // END EVENT HOOKS -------------------------------------------------------

    function initializeGA4AndTrack(order, purchaseData) {
        // Initialize gtag immediately with synchronous setup
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        window.gtag = gtag;
        gtag('js', new Date());
        
        // Configure GA4 with no user data in config
        gtag('config', TRACKING_CONFIG.ga4_measurement_id);

        // Set user properties (only customer_id, no PII)
        gtag('set', 'user_properties', {
            'customer_id': order.customer.id
        });

        // Track the upsell event with only transaction-level data
        const eventData = {
            transaction_id: purchaseData.transaction_id,
            value: parseFloat(purchaseData.value),
            currency: purchaseData.currency,
            tax: parseFloat(purchaseData.tax || 0),
            shipping: parseFloat(purchaseData.shipping || 0),
            coupon: purchaseData.coupon || '',
            purchase_type: 'upsell',
            items: purchaseData.items.map(item => ({
                item_id: item.item_id,
                item_name: item.item_name,
                category: item.category,
                quantity: parseInt(item.quantity),
                price: parseFloat(item.price),
                item_variant: item.item_variant
            }))
        };

        gtag('event', 'upsell_purchase', eventData);
        
        // Load the GA4 script asynchronously (for future events)
        const script = document.createElement('script');
        script.async = true;
        script.src = `https://www.googletagmanager.com/gtag/js?id=${TRACKING_CONFIG.ga4_measurement_id}`;
        document.head.appendChild(script);
    }

    function initializeFacebookAndTrack(order, purchaseData) {
        // Initialize Facebook Pixel with the standard implementation
        !function(f,b,e,v,n,t,s)
        {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
        n.callMethod.apply(n,arguments):n.queue.push(arguments)};
        if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
        n.queue=[];t=b.createElement(e);t.async=!0;
        t.src=v;s=b.getElementsByTagName(e)[0];
        s.parentNode.insertBefore(t,s)}(window, document,'script',
        'https://connect.facebook.net/en_US/fbevents.js');
        
        // Initialize with customer data
        fbq('init', TRACKING_CONFIG.facebook_pixel_id, {
            em: order.customer.email,
            external_id: order.customer.id.toString(),
            ga_id: '', // Can be populated if you have GA Client ID
            customer_type: order.customer.ordersCount > 1 ? 'returning' : 'new',
            ph: order.customer.phone || '',
            fn: order.customer.firstName || '',
            ln: order.customer.lastName || '',
            ct: order.billingAddress?.city || '',
            st: order.billingAddress?.province || '',
            zp: order.billingAddress?.zip || '',
            country: order.billingAddress?.country || ''
        });
        
        // Track PageView
        fbq('track', 'PageView');
        
        // Track UpsellPurchase event
        const eventData = {
            value: parseFloat(purchaseData.value),
            currency: purchaseData.currency,
            content_type: 'product_group',
            contents: purchaseData.items.map(item => ({
                id: item.item_id,
                quantity: parseInt(item.quantity),
                item_price: parseFloat(item.price)
            })),
            content_ids: purchaseData.items.map(item => item.item_id),
            num_items: purchaseData.items.reduce((sum, item) => sum + parseInt(item.quantity), 0),
            order_id: purchaseData.transaction_id,
            content_name: purchaseData.items.map(item => item.item_name).join(', ')
        };

        fbq('trackCustom', 'Upsell Purchase', eventData);
    }

    // TRACKING FUNCTIONS ----------------------------------------------------
    function onCheckoutAmended(upsellOrder, initialOrder, shopifyObject) {
        upsellCount++;
        
        // Identify which items were added to the initial order
        var initialItemIds = initialOrder.lineItems.map(function (line) { return line.id; });
        var addedItems = upsellOrder.lineItems.filter(
            function (line) { return initialItemIds.indexOf(line.id) < 0; }
        );
        
        // If no new items were added, skip tracking
        if (addedItems.length === 0) return;
        
        // Initialize tracking pixels and track immediately when ready
        initializeTrackingAndFire(upsellOrder, addedItems, initialOrder, shopifyObject);
    }

    function initializeTrackingAndFire(upsellOrder, addedItems, initialOrder, shopifyObject) {
        const purchaseData = buildPurchaseData(upsellOrder, addedItems, true, initialOrder, shopifyObject);
        
        // Initialize and track GA4 immediately
        if (TRACKING_CONFIG.enable_ga4) {
            initializeGA4AndTrack(upsellOrder, purchaseData);
        }
        
        // Initialize and track Facebook immediately
        if (TRACKING_CONFIG.enable_facebook) {
            initializeFacebookAndTrack(upsellOrder, purchaseData);
        }
    }

    // DATA BUILDING FUNCTIONS -----------------------------------------------
    function buildPurchaseData(order, addedItems, isUpsell, initialOrder, shopifyObject) {
        const revenue = isUpsell ? getAdditionalRevenue(order, initialOrder) : order.totalPrice;
        const subtotal = isUpsell ? getAdditionalSubtotal(order, initialOrder) : order.subtotalPrice;
        
        let affiliation = '';
        try {
            affiliation = new URL(shopifyObject.pageUrl).hostname;
        } catch (e) {
            affiliation = '';
        }

        return {
            transaction_id: getOrderId(order.id, isUpsell).toString(),
            order_name: getOrderId(order.number, isUpsell).toString(),
            value: revenue,
            currency: order.currency,
            tax: '0',
            shipping: (parseFloat(revenue) - parseFloat(subtotal)).toString(),
            coupon: getDiscountAmount(order, isUpsell, addedItems),
            affiliation: affiliation,
            customer_id: order.customer.id,
            customer_email: order.customer.email,
            customer_first_name: order.customer.firstName,
            customer_last_name: order.customer.lastName,
            customer_order_count: order.customer.ordersCount,
            items: buildItemsData(addedItems)
        };
    }

    function buildItemsData(lineItems) {
        return lineItems.map(function (item) {
            return {
                item_id: item.variant.sku || item.variant.id.toString(),
                item_name: item.title,
                category: item.product.type || '',
                quantity: item.quantity.toString(),
                price: item.price.toString(),
                item_variant: item.title,
                product_id: item.product.id.toString(),
                variant_id: item.variant.id.toString()
            };
        });
    }

    // UTILITY FUNCTIONS -----------------------------------------------------
    function getDiscountAmount(shopifyOrder, isUpsell, addedItems) {
        if (shopifyOrder.discounts === null || typeof shopifyOrder.discounts === 'undefined') return '0';
        if (shopifyOrder.discounts.length === 0) return '0';
        
        if (!isUpsell) {
            return shopifyOrder.discounts.reduce(function (acc, discount) {
                return acc += parseFloat(discount.amount);
            }, 0).toFixed(2).toString();
        } else {
            return addedItems.reduce(function (acc, addedItem) {
                return acc += parseFloat(addedItem.lineLevelTotalDiscount || 0);
            }, 0).toFixed(2).toString();
        }
    }

    function getOrderId(orderId, isUpsell) {
        return isUpsell ? orderId.toString() + '-US' + upsellCount.toString() : orderId;
    }

    function getAdditionalRevenue(newOrder, initialOrder) {
        return (parseFloat(newOrder.totalPrice) - parseFloat(initialOrder.totalPrice)).toFixed(2);
    }

    function getAdditionalSubtotal(newOrder, initialOrder) {
        return (parseFloat(newOrder.subtotalPrice) - parseFloat(initialOrder.subtotalPrice)).toFixed(2);
    }

    // Export functions for testing (optional)
    try {
        module.exports = exports = {
            onCheckoutAmended: onCheckoutAmended,
            resetUpsellCount: function(){upsellCount = 0;},
            initializeTrackingAndFire: initializeTrackingAndFire
        };
    } catch (e) { }

})();
</script>
<!-- End GTM-less Upsell Tracking -->

Save Changes:

  • Once you have added the required post purchase tracking code into the additional scripts checkout settings in your Shopify account and have added your personal IDs, be sure to save your changes.
    • Click on the "Save" button located in the bottom right-hand corner of the Checkout Settings page.
      • (See Figure 4)

Step 3 screenshot

_Figure 4_

Frequently Asked Questions:

Will I miss out on purchase tracking if my customer doesn't reach the Thank You Page?

You may find that many of your users don't navigate to the thank you page when you present a post-purchase upsell. If you are using our server-side tracking for available destinations this is not an issue as the server-side purchase trigger is based on the order creation within Shopify to trigger the server-side event to send. This trigger still occurs even if the customer closes out at the post-purchase step.

We recommend leaving your dl_purchase to fire on the thank_you page for a few reasons:

  • The purchase data available on the post-purchase is limited and will prevent you from capturing some information like new vs returning users.
  • There is a 500ms time limit for all tracking script to process on the post-purchase page, this prevents most web tags from firing which then leads to additional customizations for the tags that don't fire to get them to fire on the thank you page.