Shopify

Track SKU Products or Subscriptions Separately in Shopify

Learn how to track subscriptions and individual product SKUs in Shopify using Convertโ€™s native app or manual integration with custom JavaScript.

๐Ÿš€ THIS ARTICLE WILL HELP YOU:

๐Ÿ”„ Tracking Subscriptions via the Convert Shopify App

Nowadays, subscriptions can be tracked via the Convert Shopify App.
For detailed integration instructions, see:
๐Ÿ‘‰ Integrate with Shopify through our custom Convert App

The app creates two subscription goals:

  • One for tracking Subscription transactions

  • One for Non-Subscription transactions

These goals must be linked to existing goals in Convert. To set this up:

  1. Create a Custom JS goal for each type (subscription and non-subscription)

  2. Link them within the Shopify App goals tab

๐Ÿšจ Important Note: This integration classifies a transaction as a subscription if there are ANY subscription articles in the transaction, and non-subscription if there are none. This differs from separating subscription and non-subscription articles into different transactions.

๐Ÿ“ฆ Tracking Specific Products Separately by SKU

To track specific products separately by SKU, use the Manual Shopify Installation method:
๐Ÿ‘‰ Integrating Convert Experiences with Shopify

โš ๏ธ This is not intended to track all products separately by SKUโ€”only one or two that are important to you.

For this, replace the Customer Event Pixel with the following custom code:

Implementation Code

// version 10.0

const DEBUG = true; // Set to false to disable debug logs
const ENABLE_PROPERTY_FILTERING = true; // Set to true to enable property filtering

// Change to fit your own Convert goals
const purchase_goalid = '100136097';
const addToCart_goalid = '100136098';
const checkoutStarted_goalid = '100132287';
const product1_goalid = '100136099'; // Replace with your actual goal ID
const product2_goalid = '100136100'; // Replace with your actual goal ID

const TRACK_PRODUCT1 = true;
const TRACK_PRODUCT2 = true;

const filterCriteria = {
    enabled: true,
    checkExistence: ['sku'],
    matchValue: {},
    checkValue: true
};

const product1_sku = '23026961-pink-united-states-l-diameter-7-5cm';
const product2_sku = 'your-second-product-sku'; // Replace with your actual second product SKU

function updateFilterCriteria() {
    if (TRACK_PRODUCT1 && TRACK_PRODUCT2) {
        filterCriteria.enabled = false;
    } else if (TRACK_PRODUCT1) {
        filterCriteria.enabled = true;
        filterCriteria.matchValue = { 'sku': product1_sku };
    } else if (TRACK_PRODUCT2) {
        filterCriteria.enabled = true;
        filterCriteria.matchValue = { 'sku': product2_sku };
    } else {
        filterCriteria.enabled = false;
    }
}
updateFilterCriteria();

function hasProductSKU(lineItems, targetSKU) {
    if (!lineItems || !Array.isArray(lineItems)) return false;

    for (const item of lineItems) {
        const itemSKU = item.sku || (item.variant && item.variant.sku);
        if (itemSKU === targetSKU) {
            return true;
        }
    }
    return false;
}

function checkCriteria(event, criteria) {
    if (!criteria.enabled) return true;

    if (criteria.checkExistence && criteria.checkExistence.length > 0) {
        for (const prop of criteria.checkExistence) {
            if (findProperty(event, prop) === undefined) {
                debugLog(`Required property ${prop} not found in event`);
                return false;
            }
        }
    }

    if (criteria.checkValue && criteria.matchValue) {
        for (const prop in criteria.matchValue) {
            const value = findProperty(event, prop);
            if (value !== criteria.matchValue[prop]) {
                debugLog(`Property ${prop} value ${value} does not match criteria ${criteria.matchValue[prop]}`);
                return false;
            }
        }
    }

    return true;
}

function isValidJSON(data) {
    try {
        JSON.parse(data);
    } catch (e) {
        return false;
    }
    return true;
}

function debugLog(message, ...optionalParams) {
    if (DEBUG) {
        console.log('Convert Shopify Integration:', message, ...optionalParams);
    }
}

function findProperty(obj, propertyName) {
    if (obj === undefined || obj === null) return undefined;
    if (obj.hasOwnProperty(propertyName)) return obj[propertyName];
    for (const key in obj) {
        if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
            const result = findProperty(obj[key], propertyName);
            if (result !== undefined) return result;
        }
    }
    return undefined;
}

function getCookie(name) {
    const nameEQ = name + "=";
    const ca = document.cookie.split(';');
    for(let i = 0; i < ca.length; i++) {
        let c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

function getConvertAttributes(event) {
    debugLog("Starting getConvertAttributes function");

    let result = localStorage.getItem('convert_attributes');
    debugLog("LocalStorage result:", result);

    if (!result) {
        debugLog("convert_attributes not found in localStorage, checking cookie");
        result = getCookie('convert_attributes');
        debugLog("Cookie result:", result);

        if (result) {
            try {
                result = decodeURIComponent(result);
                JSON.parse(result);
                debugLog("Successfully decoded cookie value:", result);
            } catch (e) {
                console.error("Error decoding cookie value:", e);
                result = null;
            }
        } else if (event?.data?.checkout) {
            result = findProperty(event.data.checkout, 'customAttributes');
            if (result && typeof result !== 'string') {
                result = JSON.stringify(result);
            }
        }
    }

    if (result) {
        try {
            const parsed = JSON.parse(typeof result === 'string' ? result : JSON.stringify(result));
            return JSON.stringify(parsed);
        } catch (e) {
            console.error("Error validating final result:", e);
            return null;
        }
    }

    return null;
}

function postConversion(convert_attributes_str, goalid) {
    debugLog('Starting postConversion function with goal id:', goalid);

    if (!convert_attributes_str) return;

    try {
        if (convert_attributes_str.indexOf('%') !== -1) {
            convert_attributes_str = decodeURIComponent(convert_attributes_str);
        }
        const convert_attributes = JSON.parse(convert_attributes_str);
        if (!convert_attributes || Object.keys(convert_attributes).length === 0) return;

        const post = {
            'cid': convert_attributes.cid,
            'pid': convert_attributes.pid,
            'seg': convert_attributes.defaultSegments,
            's': 'shopify',
            'vid': convert_attributes.vid,
            'ev': [{
                'evt': 'hitGoal',
                'goals': [goalid],
                'exps': convert_attributes.exps,
                'vars': convert_attributes.vars
            }]
        };

        const data = JSON.stringify(post);
        const beaconUrl = `https://${convert_attributes.pid}.metrics.convertexperiments.com/track`;

        if (navigator.sendBeacon) {
            const blob = new Blob([data], {type: 'application/json'});
            navigator.sendBeacon(beaconUrl, blob);
        } else {
            fetch(beaconUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: data,
                keepalive: true
            });
        }
    } catch (e) {
        console.error('Error parsing JSON in postConversion:', e);
    }
}

async function postTransaction(convert_attributes_str, purchase_event, purchase_goalid) {
    debugLog("Starting postTransaction function.");

    if (!convert_attributes_str || !purchase_event) return;

    try {
        if (convert_attributes_str.indexOf('%') !== -1) {
            convert_attributes_str = decodeURIComponent(convert_attributes_str);
        }

        const convert_attributes = JSON.parse(convert_attributes_str);
        if (!convert_attributes || Object.keys(convert_attributes).length === 0) return;

        const lineItems = purchase_event.data.checkout.lineItems;
        const hasProduct1 = hasProductSKU(lineItems, product1_sku);
        const hasProduct2 = hasProductSKU(lineItems, product2_sku);

        if (TRACK_PRODUCT1 && hasProduct1) {
            await postConversion(convert_attributes_str, product1_goalid);
        }

        if (TRACK_PRODUCT2 && hasProduct2) {
            await postConversion(convert_attributes_str, product2_goalid);
        }

        let transactionAmount = parseFloat(purchase_event.data.checkout.totalPrice.amount);
        const originalTransactionAmount = transactionAmount;
        let baseCurrencyAmount = null;

        if (purchase_event.data.checkout.totalPrice?.shopMoney) {
            baseCurrencyAmount = parseFloat(purchase_event.data.checkout.totalPrice.shopMoney.amount);
        } else if (purchase_event.data.checkout.shop_money_total_price) {
            baseCurrencyAmount = parseFloat(purchase_event.data.checkout.shop_money_total_price);
        } else if (purchase_event.data.checkout.currencyCode && purchase_event.data.checkout.presentmentCurrencyRate) {
            const rate = parseFloat(purchase_event.data.checkout.presentmentCurrencyRate);
            baseCurrencyAmount = rate !== 1 ? originalTransactionAmount / rate : originalTransactionAmount;
        } else if (purchase_event.data.checkout.currency_rate) {
            const rate = parseFloat(purchase_event.data.checkout.currency_rate);
            baseCurrencyAmount = rate !== 1 ? originalTransactionAmount / rate : originalTransactionAmount;
        }

        transactionAmount = baseCurrencyAmount || transactionAmount;

        if (transactionAmount >= convert_attributes.min_order_value && transactionAmount <= convert_attributes.max_order_value) {
            const transactionId = purchase_event.data.checkout.order.id;

            const post = {
                'cid': convert_attributes.cid,
                'pid': convert_attributes.pid,
                'seg': convert_attributes.defaultSegments,
                's': 'shopify',
                'vid': convert_attributes.vid,
                'tid': transactionId,
                'ev': [{
                    'evt': 'tr',
                    'goals': [purchase_goalid],
                    'exps': convert_attributes.exps,
                    'vars': convert_attributes.vars,
                    'r': transactionAmount,
                    'prc': lineItems.length
                }]
            };

            const data = JSON.stringify(post);
            const beaconUrl = `https://${convert_attributes.pid}.metrics.convertexperiments.com/track`;

            const response = await fetch(beaconUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: data
            });

            const result = await response.json();
            debugLog("transactionID: " + transactionId);
            debugLog("purchase_event: " + JSON.stringify(purchase_event.data));
        }
    } catch (e) {
        console.error('Error parsing JSON in postTransaction:', e);
    }
}

analytics.subscribe("checkout_completed", async (event) => {
    debugLog("Event received for checkout_completed.");
    try {
        const result = getConvertAttributes(event);
        if (!result) return;
        await postConversion(result, purchase_goalid);
        await postTransaction(result, event, purchase_goalid);
    } catch (error) {
        console.error('Error in checkout_completed promise chain:', error);
    }
});

analytics.subscribe("checkout_started", async (event) => {
    debugLog("Event received for checkout_started.");
    try {
        let result = getConvertAttributes(event);
        if (!result) return;
        await postConversion(result, checkoutStarted_goalid);
    } catch (error) {
        console.error('Error in checkout_started promise chain:', error);
    }
});

KEY POINTS TO REMEMBER

โœ… For Subscription Tracking:

๐ŸŽฏ For Product-Specific SKU Tracking:

  • Use the Manual Installation

  • Create Custom JS goals for the products you want to track

  • Update the provided code with your SKUs and goal IDs

  • Replace the default Customer Event Pixel