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