Integrate ABlyftTracking & eventsShopify tracking

Shopify customer event pixel

Track revenue, checkout steps and product-level goals in Shopify with a custom pixel.

With a custom pixel (Shopify Customer events), ABlyft collects Shopify's client-side events through the Web Pixel API. This also lets you follow the steps of the checkout, not only the purchase. See Shopify's documentation on customer events and pixels for background.

Customer events can be blocked and are less reliable than the webhook. See the comparison before you combine both.

Set it up

  1. In Shopify, open Settings → Customer events.
  2. Choose Add custom pixel and give it a name, for example ABlyft Tracking.
  3. Paste the code below and change the projectId to the ID of your ABlyft project. This is required.
  4. Adjust the eventName of each goal in the code if it differs from the API names in ABlyft.
  5. Add the pixel, save it and connect it.

To track purchases and similar events, check the Customer privacy settings of the pixel in Shopify. Events are only sent if the setting allows it for the visitor.

Code to copy

Settings at the top of the code:

ConstantMeaning
projectIdThe ID of your ABlyft project. Replace the placeholder.
trackingCurrencyThe currency in which revenue is tracked. Default "EUR". Orders in another currency are converted.
maxOrderValueFallbackfalse by default. If you enter a number, for example 250.50, a revenue above it is capped to that value.
/*
IMPORTANT: Edit the Project ID
*/

const projectId = 123456789; // ← Your project id

//Further options (do not change if not necessary)
const trackingCurrency  = "EUR"; // ← currency you want to track in
const maxOrderValueFallback = false; // ← if a number (i.e., 250.50) is set, a revenue is capped to that

analytics.subscribe("all_events", async ({ name, data, context }) => {
    if (!(name.startsWith("checkout_") || name === "product_added_to_cart")) {
        return;
    }
    console.log("ABlyft - Shopify pixel event received:", name);

    const snippetVersion = 2.30;
    const d = document;
    const w = window;

    w.insideShopifyPixel = true;
    w.ablyftAllowIframe = true;
    w.shopifyEventContext = context;
    let j = d.createElement("script");
    j.async = true;
    j.src = `https://cdn.ablyft.com/s/${projectId}.js`;

    j.onload = async () => {
        const url = context.window.location.href;
        const { checkout } = data;
        let events = [{ eventType:"custom", eventName:`shopify_event_${name}`}];

        //currency conversion
        try {
            await new Promise((resolve, reject) => {
                const timeout = setTimeout(() => {
                    reject(new Error("ABlyft - Timeout loading currency script"));
                }, 1000); // 1 sec timeout
                const fxScript = document.createElement("script");
                fxScript.src = "https://cdn.shopify.com/s/javascripts/currencies.js";
                fxScript.onload = () => {
                    clearTimeout(timeout);
                    if (typeof Currency?.convert === "function") {
                        resolve();
                    } else {
                        reject(new Error("ABlyft - Currency.convert impossible"));
                    }
                };
                fxScript.onerror = () => {
                    clearTimeout(timeout);
                    reject(new Error("ABlyft - Currency script could not be loaded"));
                };
                document.head.appendChild(fxScript);
            });
        } catch (error) {
            console.warn("ABlyft - Currency conversion not available:", error);
        }

        //event handling
        if(name === "checkout_completed"){
            const { amount: rawAmount, currencyCode: sourceCurrency } = checkout.totalPrice;
            const count = checkout.lineItems.reduce((sum, {quantity}) => sum + quantity, 0);
            let revenue = rawAmount;
            let conversionFactor = 1;
            if(sourceCurrency !== trackingCurrency) {
                try {
                    if (typeof Currency?.convert === "function") {
                        revenue = Currency.convert(rawAmount, sourceCurrency, trackingCurrency);
                        conversionFactor = revenue / rawAmount;
                        console.log(`ABlyft - Currency conversion successful: from ${sourceCurrency} ${rawAmount} to ${trackingCurrency} ${parseFloat(revenue.toFixed(2))}`);
                    } else {
                        throw new Error("ABlyft - Currency.convert not available");
                    }
                } catch (e) {
                    console.warn("ABlyft - Currency conversion failed – using rawAmount", e);
                    revenue = rawAmount;
                }
            }
            if(typeof maxOrderValueFallback === "number"){
                if(revenue > maxOrderValueFallback) revenue = maxOrderValueFallback;
            }
            //use your own eventNames here
            events = events.concat([
                { eventType:"revenue", eventName:"revenue", eventValue: parseFloat(revenue.toFixed(2)) },
                { eventType:"custom",  eventName:"products-count", eventValue: count },
                { eventType:"custom",  eventName:"purchase" }
            ]);
            //custom product-related goals
            let lineItemsMap = {};
            checkout.lineItems.forEach(function(item) {
                const productId = item.variant?.product?.id || item.id;
                const amount =
                    item.finalLinePrice?.amount ??
                    item.totalPrice?.amount ??
                    item.linePrice?.amount ??
                    item.discountedTotalPrice?.amount ??
                    item.originalTotalPrice?.amount ??
                    0;
                const revenue = Number(amount) || 0;
                if (!lineItemsMap[productId]) {
                    lineItemsMap[productId] = { productId, revenue: 0 };
                }
                lineItemsMap[productId].revenue += revenue;
            });
            let lineItemsArray = Object.values(lineItemsMap);
            lineItemsArray.forEach(function(item) {
                events.push({ eventType: "revenue", eventName: "revenue-product-" + item.productId, eventValue: parseFloat((item.revenue*conversionFactor).toFixed(2)) });
                events.push({ eventType: "custom", eventName: "purchase-product-" + item.productId });
            });
        }
        console.log("ABlyft - Events to send:", events);

        let storageType = w.ablyft.get("data").storage;
        if(storageType === "default") storageType = "cookie";

        async function getStorageItem(key) {
            if(storageType === "cookie"){
                return await browser.cookie.get(key);
            }
            return await browser[storageType].getItem(key);
        }

        async function setStorageItem(key, value) {
            if(storageType === "cookie"){
                return await browser.cookie.set(key, value);
            }
            return await browser[storageType].setItem(key, value);
        }

        let userData = await getStorageItem("ablyft_uvs");

        const user = JSON.parse(userData);
        let visitor_type = "new";
        if (user && user.sessions && user.sessions > 1) {
            visitor_type = "returning";
        }

        const attributes = {
            device_type: w.ablyft.get("deviceType"),
            visitor_type,
            url,
            user_agent: w.navigator.userAgent
        };

        let experimentsBucketData = await getStorageItem("ablyft_exps");
        if(!experimentsBucketData){
            console.log("ABlyft - Empty user bucketing - aborting");
            return;
        }
        const experimentsBucket = JSON.parse(experimentsBucketData);

        const experimentIds = Object.keys(experimentsBucket);
        let variationIds = [];
        for (let i = 0; i < experimentIds.length; i++) { variationIds.push(experimentsBucket[experimentIds[i]]); }

        function createLockId() {
            const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            let text = "";
            for (let i = 0; i < 10; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); }
            return text;
        }

        const goals = w.ablyft.get("goals");
        const tgoalsData = await getStorageItem("ablyft_tgoals");
        const triggeredGoals = tgoalsData ? JSON.parse(tgoalsData) : {};

        const postObj = {
            projectId: projectId,
            source: "shopify-webpixel",
            snippetVersion,
            user: {
                attributes,
                experimentsBucket
            },
            events: events.filter(event => {
                const goalObject = goals.find(goal => goal.api_name === event.eventName);
                if(!goalObject) return false;
                return !(goalObject.counting_method === "one" && triggeredGoals[goalObject.id]);
            }).map(event => {
                const goalObject = goals.find(goal => goal.api_name === event.eventName);
                return {
                    type: event.eventType,
                    value: event.eventValue,
                    timestamp: Math.floor(Date.now() / 1000),
                    locked: Math.floor(Date.now() / 1000),
                    lockId: createLockId(),
                    goal: {
                        id: goalObject.id,
                        variations: variationIds
                    }
                };
            })
        }

        if(postObj.events.length === 0) return;
        const sent = await sendWithRetry(postObj);

        if(sent){
            postObj.events.forEach(event => {
                const goalId = event.goal.id;
                const existing = triggeredGoals[goalId] || [];
                triggeredGoals[goalId] = Array.from(new Set([...existing, ...variationIds]));
            });
            await setStorageItem("ablyft_tgoals", JSON.stringify(triggeredGoals));
        }

        async function sendWithRetry(data, maxRetries = 3) {
            const payload = JSON.stringify(data);
            for (let attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    const response = await fetch("https://log.ablyft.com", {
                        method: "POST",
                        headers: {
                            "Content-Type": "application/json"
                        },
                        body: payload
                    });
                    if (response.ok) {
                        console.log(`ABlyft - Request successful on attempt ${attempt}`);
                        return true;
                    } else if (response.status >= 400 && response.status < 500) {
                        console.warn(`ABlyft - Client error (${response.status}) – no retry`);
                        return false;
                    } else {
                        console.warn(`ABlyft - Server error (${response.status}) – retrying...`);
                    }
                } catch (error) {
                    console.warn(`ABlyft - Network error on attempt ${attempt}:`, error);
                }
                if (attempt < maxRetries) {
                    await new Promise((res) => setTimeout(res, 500)); //delay in ms
                }
            }

            console.error("ABlyft - All retry attempts failed.");
            return false;
        }
    };

    d.head.appendChild(j);
});

What the pixel sends

  • It reacts to the events product_added_to_cart and all checkout_* events, loads your ABlyft snippet and sends the events to ABlyft. It only sends events for goals that exist in your project (matched by API name).
  • On checkout_completed it sends the goals revenue, products-count and purchase, and the product-level goals described below.
  • The visitor must already be assigned to experiments (the pixel reads the assignment from the ABlyft storage). If there is none, nothing is sent.
  • A failed request is retried up to three times (not for client errors).

Other events

Create a Custom goal and use one of these names as its API name:

API nameFires when
shopify_event_product_added_to_cartA product is added to the cart
shopify_event_checkout_startedThe checkout is started
shopify_event_checkout_contact_info_submittedContact information is submitted
shopify_event_checkout_address_info_submittedThe address is submitted
shopify_event_checkout_shipping_info_submittedShipping information is submitted
shopify_event_checkout_completedThe checkout is completed (alternatively use purchase or revenue)

Product-level events

For measurement per product, the pixel sends two additional events for every product in a completed order:

API nameMeaning
revenue-product-[productId]Revenue of this product
purchase-product-[productId]Purchases containing this product

Create a goal and replace the placeholder with the Shopify product ID, for example revenue-product-1234567890. The values are summed per product, not per variant: the revenues of all variants are collected under one product ID.

On this page