• Shopify Tracking

    Table of contents

    In short

    Connect Shopify directly with Varify and analyze your experiments directly with the conversion and revenue data from Shopify. The integration only works together with the use of GA4, as the number of visitors is imported from GA4.

    How to use Shopify tracking data

    Step 1: Activate the Shopify data import

    Go to "Tracking Setup" and click on the "Advanced Setup" tab. Activate the "Use Shopify Data" setting here.

    Step 2: Create a custom pixel with Varify tracking code

    Click on "Settings" in your Shopify store and open "Customer events". Then click on "Add custom pixel" and name the pixel: "varify-checkout". Then insert the following code:

    Standard Code

    Use this code if your Shopify store only accepts payments in one currency.

    				
    					let storage;
    async function getVarifyData() {
      if (storage) {
        return storage;
      }
      storage = (async () => {
        try {
          const raw = await browser.localStorage.getItem('varify-data');
          return raw ? JSON.parse(raw) : {};
        } catch (error) {
          // Cache nicht dauerhaft vergiften: nach Fehler zuruecksetzen,
          // damit der naechste Aufruf erneut versucht statt still tot zu bleiben.
          storage = undefined;
          console.error('Varify: error reading local storage', error);
          return {};
        }
      })();
      return storage;
    }
    
    analytics.subscribe('varify', async (event) => {
      const {
        varify_experimentId,
        varify_variationId,
        teamId,
        storageType = 'localStorage',
      } = event.customData || {};
    
      if (!varify_experimentId || !teamId) {
        console.warn('Varify: event missing experimentId or teamId.');
        return;
      }
    
      const validVariationId =
        varify_variationId !== undefined ? varify_variationId : null;
    
      const existingData = await getVarifyData();
    
      if (!existingData.data) {
        existingData.data = [];
      }
    
      const experimentEntry = {
        experimentId: varify_experimentId,
        variationId: validVariationId,
        timestamp: Date.now(),
      };
    
      existingData.data = existingData.data.filter(
        (entry) => entry.experimentId !== varify_experimentId
      );
    
      existingData.data.push(experimentEntry);
      existingData.teamId = teamId;
      existingData.storageType = storageType;
    
      await browser.localStorage.setItem(
        'varify-data',
        JSON.stringify(existingData)
      );
    });
    
    const WINDOW_MS = 28 * 24 * 60 * 60 * 1000;
    
    const pruneEntries = (entries) => {
      const threshold = Date.now() - WINDOW_MS;
      return (entries || []).filter(
        (entry) => entry.timestamp && entry.experimentId && entry.timestamp >= threshold
      );
    };
    
    analytics.subscribe('checkout_started', async (event) => {
      const existingData = await getVarifyData();
      if (!existingData.data || existingData.data.length === 0) {
        return;
      }
    
      existingData.data = pruneEntries(existingData.data);
    
      await browser.localStorage.setItem(
        'varify-data',
        JSON.stringify(existingData)
      );
    });
    
    analytics.subscribe('checkout_completed', async (event) => {
      const orderId = event.data?.checkout?.order?.id;
      const orderRevenue = event.data?.checkout?.subtotalPrice?.amount;
    
      // 0 is a valid revenue (100% discount) and must not count as missing.
      if (!orderId || orderRevenue == null) {
        console.warn('Varify: checkout_completed missing orderId or revenue.');
        return;
      }
    
      const parsedData = await getVarifyData();
      if (!parsedData.data || parsedData.data.length === 0 || !parsedData.teamId) {
        console.warn('Varify: no valid data in local storage.');
        return;
      }
    
      // The order-status page refires checkout_completed on every visit.
      if (!Array.isArray(parsedData.sentOrders)) {
        parsedData.sentOrders = [];
      }
      if (parsedData.sentOrders.includes(orderId)) {
        return;
      }
    
      parsedData.data = pruneEntries(parsedData.data);
      if (parsedData.data.length === 0) {
        return;
      }
    
      const storedAccountId = parsedData.teamId;
      const experiments = parsedData.data.map((entry) => {
        return [entry.experimentId, entry.variationId];
      });
    
      const storageType = parsedData.storageType;
    
      const storeHandle = init.data?.shop?.myshopifyDomain?.replace('.myshopify.com', '');
    
      const lineItems = event.data?.checkout?.lineItems;
      let cartProducts = [];
    
      if (lineItems && Array.isArray(lineItems)) {
        cartProducts = lineItems
          .map((item) => {
            const productId = item.variant?.product?.id?.split("/").pop();
            const variantId = item.variant?.id?.split("/").pop();
    
            if (productId && variantId) {
              return {
                productId,
                variantId,
              };
            }
            return null;
          })
          .filter((p) => p !== null);
      }
    
      try {
        await fetch('https://ecommerce.varify.io/store_data', {
          method: 'POST',
          keepalive: true,
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            experiments,
            orderId,
            orderRevenue,
            accountId: storedAccountId,
            storageType: storageType,
            storeHandle,
            cartProducts,
          }),
        });
    
        parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
        await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
      } catch (error) {
        console.error('Varify: error sending data', error);
      }
    });
    				
    			

    Multi-Currency Code

    Use this code if your Shopify store supports payments in multiple currencies.

    				
    					let storage;
    
    const SHOP_CURRENCY = init.data?.shop?.paymentSettings?.currencyCode || 'EUR';
    
    const FX_API = 'https://api.frankfurter.dev/v1/latest';
    const FX_TTL_MS = 6 * 60 * 60 * 1000;
    const FX_TIMEOUT_MS = 2000;
    
    async function getVarifyData() {
      if (storage) {
        return storage;
      }
      storage = (async () => {
        try {
          const raw = await browser.localStorage.getItem('varify-data');
          return raw ? JSON.parse(raw) : {};
        } catch (error) {
          // Reset so the next call retries instead of caching a poisoned promise.
          storage = undefined;
          console.error('Varify: error reading local storage', error);
          return {};
        }
      })();
      return storage;
    }
    
    // Returns units of <currency> per 1 <SHOP_CURRENCY>, or null. null => do not send.
    async function getFxRate(currency) {
      const cacheKey = `varify-fx-${SHOP_CURRENCY}-${currency}`;
    
      try {
        const cached = await browser.localStorage.getItem(cacheKey);
        if (cached) {
          const { rate, ts } = JSON.parse(cached);
          if (rate && ts && Date.now() - ts < FX_TTL_MS) {
            return rate;
          }
        }
      } catch (error) {
        // Broken cache -> fetch fresh below.
      }
    
      try {
        const url = `${FX_API}?from=${encodeURIComponent(SHOP_CURRENCY)}&to=${encodeURIComponent(currency)}`;
    
        let res;
        if (typeof AbortController === 'function') {
          const controller = new AbortController();
          const timer = setTimeout(() => controller.abort(), FX_TIMEOUT_MS);
          try {
            res = await fetch(url, { signal: controller.signal });
          } finally {
            clearTimeout(timer);
          }
        } else {
          res = await fetch(url);
        }
    
        if (!res.ok) {
          console.warn(`Varify: FX API returned ${res.status} for ${currency}.`);
          return null;
        }
    
        const json = await res.json();
        const rate = json?.rates?.[currency];
    
        if (typeof rate !== 'number' || !isFinite(rate) || rate <= 0) {
          console.warn(`Varify: FX API gave no usable rate for ${currency}.`, json);
          return null;
        }
    
        await browser.localStorage.setItem(
          cacheKey,
          JSON.stringify({ rate, ts: Date.now() })
        );
        return rate;
      } catch (error) {
        console.warn(`Varify: FX lookup failed for ${currency}.`, error);
        return null;
      }
    }
    
    analytics.subscribe('varify', async (event) => {
      const {
        varify_experimentId,
        varify_variationId,
        teamId,
        storageType = 'localStorage',
      } = event.customData || {};
    
      if (!varify_experimentId || !teamId) {
        console.warn('Varify: event missing experimentId or teamId.');
        return;
      }
    
      const validVariationId =
        varify_variationId !== undefined ? varify_variationId : null;
    
      const existingData = await getVarifyData();
    
      if (!existingData.data) {
        existingData.data = [];
      }
    
      const experimentEntry = {
        experimentId: varify_experimentId,
        variationId: validVariationId,
        timestamp: Date.now(),
      };
    
      existingData.data = existingData.data.filter(
        (entry) => entry.experimentId !== varify_experimentId
      );
    
      existingData.data.push(experimentEntry);
      existingData.teamId = teamId;
      existingData.storageType = storageType;
    
      await browser.localStorage.setItem(
        'varify-data',
        JSON.stringify(existingData)
      );
    });
    
    const WINDOW_MS = 28 * 24 * 60 * 60 * 1000;
    
    const pruneEntries = (entries) => {
      const threshold = Date.now() - WINDOW_MS;
      return (entries || []).filter(
        (entry) => entry.timestamp && entry.experimentId && entry.timestamp >= threshold
      );
    };
    
    analytics.subscribe('checkout_started', async (event) => {
      const existingData = await getVarifyData();
      if (!existingData.data || existingData.data.length === 0) {
        return;
      }
    
      existingData.data = pruneEntries(existingData.data);
    
      await browser.localStorage.setItem(
        'varify-data',
        JSON.stringify(existingData)
      );
    
      // Warm the rate while the customer is still typing.
      const currency = event.data?.checkout?.currencyCode;
      if (currency && currency !== SHOP_CURRENCY) {
        getFxRate(currency);
      }
    });
    
    analytics.subscribe('checkout_completed', async (event) => {
      const orderId = event.data?.checkout?.order?.id;
      const money = event.data?.checkout?.subtotalPrice;
      let orderRevenue = money?.amount;
      const orderCurrency = money?.currencyCode;
    
      // 0 is a valid revenue (100% discount) and must not count as missing.
      if (!orderId || orderRevenue == null || !orderCurrency) {
        console.warn('Varify: checkout_completed missing orderId, revenue or currency.');
        return;
      }
    
      // subtotalPrice is presentment currency; the event carries no rate or shop-money
      // amount, and /store_data books the number as shop currency. Convert here, or skip.
      if (orderCurrency !== SHOP_CURRENCY) {
        const rate = await getFxRate(orderCurrency);
        if (!rate) {
          console.warn(
            `Varify: skipping order ${orderId} — ${orderRevenue} ${orderCurrency}, no FX rate available.`
          );
          return;
        }
        const converted = +(orderRevenue / rate).toFixed(2);
        console.info(
          `Varify: converted order ${orderId} — ${orderRevenue} ${orderCurrency} @ ${rate} -> ${converted} ${SHOP_CURRENCY}.`
        );
        orderRevenue = converted;
      }
    
      const parsedData = await getVarifyData();
      if (!parsedData.data || parsedData.data.length === 0 || !parsedData.teamId) {
        console.warn('Varify: no valid data in local storage.');
        return;
      }
    
      // The order-status page refires checkout_completed on every visit.
      if (!Array.isArray(parsedData.sentOrders)) {
        parsedData.sentOrders = [];
      }
      if (parsedData.sentOrders.includes(orderId)) {
        return;
      }
    
      parsedData.data = pruneEntries(parsedData.data);
      if (parsedData.data.length === 0) {
        return;
      }
    
      const storedAccountId = parsedData.teamId;
      const experiments = parsedData.data.map((entry) => {
        return [entry.experimentId, entry.variationId];
      });
    
      const storageType = parsedData.storageType;
    
      const storeHandle = init.data?.shop?.myshopifyDomain?.replace('.myshopify.com', '');
    
      const lineItems = event.data?.checkout?.lineItems;
      let cartProducts = [];
    
      if (lineItems && Array.isArray(lineItems)) {
        cartProducts = lineItems
          .map((item) => {
            const productId = item.variant?.product?.id?.split("/").pop();
            const variantId = item.variant?.id?.split("/").pop();
    
            if (productId && variantId) {
              return {
                productId,
                variantId,
              };
            }
            return null;
          })
          .filter((p) => p !== null);
      }
    
      try {
        await fetch('https://ecommerce.varify.io/store_data', {
          method: 'POST',
          keepalive: true,
          headers: {
            'Content-Type': 'application/json',
          },
    
          body: JSON.stringify({
            experiments,
            orderId,
            orderRevenue,
            // Already converted above; always shop currency so a future server-side
            // conversion is a no-op, not a double conversion.
            orderCurrency: SHOP_CURRENCY,
            accountId: storedAccountId,
            storageType: storageType,
            storeHandle,
            cartProducts,
          }),
        });
    
        parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
        await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
      } catch (error) {
        console.error('Varify: error sending data', error);
      }
    });
    				
    			

    After you have added the code, click on save. Also check that your permissions are set correctly, otherwise the script will not be executed if a user permission is missing. Then click on "Connect". The integration is now successfully installed.

    Step 3: Add Shopify Goals to your experiment

    Make sure that your tracking setup is set up and that you have selected GA4 as the tracking provider and that the "Test Evaluation" item is set to "In Varify and GA4". If this is the case, a "Results Link" will now appear for "newly created" experiments at the top right of the started experiment. 

    Clicking on the "Results Link" opens the corresponding results report of the experiment. Now you can add either "Conversions" or "Revenue" as a goal by clicking on "Add Shopify Goal". The visitors of the goal are taken from GA4. The conversion value and the revenue value come directly from Shopify.

    Important: GA4 data is only updated after approx. 24 hours. The data from Shopify is updated in real time.

    Product filter in the results report

    Analyze the performance of certain products in your A/B test. You can either look at individual products or product variants. To do this, enter a product ID or several IDs in the field, separated by commas.

  • First steps