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

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 Add Upsell Event Tracking in Your Destinations

Locate Post-Purchase Page Settings:

  • Begin on your Shopify homepage and use the left-hand navigation menu to click on the "Settings" button or the gear icon located in the lower left-hand corner of the page.
    • Using the left-hand menu, locate and click on the "Checkout" tab.
    • Navigate to the section of the page titled "Post-purchase page".
      • (See Figure 1)

Step 1 screenshotFigure 1

Add Code:

  • Copy the script below and add it in the "Additional Scripts" box of your Shopify Post-purchase page checkout settings.
    • Be sure to update the GTM-xxxxx near the top of the script with your Web Container ID. Follow this guide to learn how to locate your Container ID.
    • Once you have added the new code, be sure to save your changes by clicking on the "Save" button located in the bottom right-hand corner of the Checkout Settings page.
<!-- Begin Elevar Upsell Tracking -->
<script>
// Fires an dl_upsell_purchase event when customer takes upsell offer

(function (w, d, s, l, i) {
    w[l] = w[l] || []; w[l].push({
        'gtm.start':
            new Date().getTime(), event: 'gtm.js'
    }); var f = d.getElementsByTagName(s)[0],
        j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src =
            'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', 'GTM-XXX');

var upsellCount = 0;
(function() {
// EVENT HOOKS -----------------------------------------------------------
if (!Shopify.wasPostPurchasePageSeen) {
    onCheckout(window.Shopify.order, window.Shopify);
}

Shopify.on('CheckoutAmended', function (newOrder, initialOrder) {
    onCheckoutAmended(newOrder, initialOrder, window.Shopify);
});
// END EVENT HOOKS -------------------------------------------------------

// UTILS -----------------------------------------------------------------
// Function called after original order is placed, pre upsell.
function onCheckout(initialOrder, shopifyObject) {
    window.dataLayer = window.dataLayer || [];
    pushDLPurchase(initialOrder, initialOrder.lineItems, false, null, shopifyObject);
}

// Function called when upsell is taken. Separate the new/upsell
// items from the items in the initial order and then send a purchase event
// for just the new items.
function onCheckoutAmended(upsellOrder, initialOrder, shopifyObject) {
    // identify which items were added to the initial order, if any.
    upsellCount++;
    // The line item id is unique for order items, even if the items contained are the same.
    // We can use this to seperate out items from the initial order from the upsell.
    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;
    pushDLPurchase(upsellOrder, addedItems, true, initialOrder, shopifyObject);
}

function pushDLPurchase(order, addedItems, isUpsell, initialOrder, shopifyObject) {
    window.dataLayer.push({
        'event': isUpsell ? 'dl_upsell_purchase' : '',
        'event_id': getOrderId(order.id, isUpsell),
        'user_properties': getUserProperties(order),
        'ecommerce': {
            'purchase': {
                'actionField': getActionField(order, isUpsell, initialOrder, addedItems, shopifyObject),
                'products': getLineItems(addedItems),
            },
            'currencyCode': order.currency,
        },
    });
}
// Returns a user properties object
function getUserProperties(data) {
    return {
        'customer_id': data.customer.id,
        'customer_email': data.customer.email,
        'customer_first_name': data.customer.firstName,
        'customer_last_name': data.customer.lastName,
        'customer_order_count': data.customer.ordersCount,
    }
}

// Gets line items in purchase
function getLineItems(lineItems) {
    return lineItems.map(function (item) {
        return {
            'category': item.product.type,
            'variant_id': item.variant.id.toString(),
            'product_id': Number(item.product.id).toString(),
            'id': item.variant.sku,
            // We don't get variant title details
            'variant': item.title,
            'name': item.title,
            'price': item.price.toString(),
            'quantity': item.quantity.toString(),
            // Not available
            // 'brand': orderItem.brand,
        }
    });
}

function getActionField(order, isUpsell, initialOrder, addedItems, shopifyObject) {
    var revenue = isUpsell ? getAdditionalRevenue(order, initialOrder) : order.totalPrice;
    var subtotal = isUpsell ? getAdditionalSubtotal(order, initialOrder) : order.subtotalPrice;
    try {
        affiliation = new URL(shopifyObject.pageUrl).hostname;
    } catch (e){
        affiliation = '';
    }
    return {
        'action': "purchase",
        'affiliation': affiliation,
        // This is the longer order id that shows in the url on an order page
        'id': getOrderId(order.id, isUpsell).toString(),
        // This should be the #1240 that shows in order page.
        'order_name': getOrderId(order.number, isUpsell).toString(),
        // This is total discount. Dollar value, not percentage
        // On the first order we can look at the discounts object. On upsells, we can't.
        // This needs to be a string.
        'discount_amount': getDiscountAmount(order, isUpsell, addedItems),
        // We can't determine shipping & tax. For the time being put the difference between subtotal and rev in shipping
        'shipping': (parseFloat(revenue) - parseFloat(subtotal)).toString(),
        'tax': '0',
        'revenue': revenue,
        'sub_total': subtotal,
    };
}

function getDiscountAmount(shopifyOrder, isUpsell, addedItems) {
    if (shopifyOrder.discounts === null || typeof shopifyOrder.discounts === 'undefined') return '0';
    if (shopifyOrder.discounts.length === 0) return '0';
    // If this isn't an upsell we can look at the discounts object.
    if (!isUpsell) {
        // Collect all the discounts on the first order.
        return shopifyOrder.discounts.reduce(function (acc, discount) {
            return acc += parseFloat(discount.amount);
        }, 0).toFixed(2).toString();
    // If this an upsell we have to look at the line item discounts
    // The discount block provided doesn't only applies to the first order.
    } else {
        return addedItems.reduce(function (acc, addedItem) {
            return acc += parseFloat(addedItem.lineLevelTotalDiscount);
        }, 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);
}

function test() {
    onCheckoutAmended(newOrder, initialOrder);
}

try {
    module.exports = exports = {
        onCheckoutAmended: onCheckoutAmended,
        onCheckout: onCheckout,
        resetUpsellCount: function(){upsellCount = 0;},
    };
} catch (e) { }

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

Import Pre-Built Container(s)

Access Pre-Built Containers:

  • Begin on the homepage of your Elevar app. Use the left-hand navigation menu and click on the tab titled "Pre-Build Tags".
  • Use the search function to locate and click on the "Upsell Funnel Events" Container.
    • Once in the Upsell Funnel Events Container, navigate to the box titled "Download Container".
    • Click on the button labeled "Download Container".
      • (See Figure 2)

Step 1 screenshot

_Figure 2_

Import Container into GTM:

  • Once you have downloaded the Upsell Funnel Events Container, you will need to import the Container into your Google Tag Manager account. Follow this guide to learn how to import a pre-built container into Google Tag Manager.
    • (See Figure 3)

Step 1 screenshot

`_Figure 3_

👍

Understanding the Container:

  • In the default setup, there is a Facebook Upsell Purchase tag to the dl_upsell_purchase event. This sends a custom conversion event so we dont inflate Facebook Purchase conversions.
  • We also attach a custom GA event to the upsell that hyphenates the original order id (for ex. order 1234-US1). This lets you create a view in GA that filters out upsells and prevents inflated conversion rates.

Sending Upsell Purchase Events to Other Channels:

  • If you are already using Elevar's pre-built container for other channels, then you only need to account for the dl_upsell_purchase events.
    • For example, let's say you have Google Ads configured in GTM for primary purchase events, but you want to send a Google Ads conversion for upsell purchases as well.
    • You can create a new conversion in Google Ads and assign to the same upsell_purchase trigger used by Facebook.
      • (See Figure 4)

Figure 4

Publish Updates and QA

  • One big limitation with the new post-purchase page settings is that you can't use GTM preview mode since it's sandboxed JavaScript.
    • This means you'll need to publish your container, place a test order, and verify using dev tools in your browser OR inside your events manager.

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.