• Seguimiento en tiempo real de Varify

    Las cuentas creadas a partir del 17 de septiembre de 2026 pueden activar la función „Varify Realtime Tracking“ directamente en „Configuración de seguimiento“. Los clientes actuales pueden solicitar esta función enviando una solicitud a [email protected].

    Índice

    Corto y dulce

    Activa el seguimiento en tiempo real de Varify y evalúa tus experimentos directamente y sin demora en Varify. Un servicio de seguimiento propio de Varify mide los objetivos que hayas definido y los muestra en los informes de Varify.

    Cómo utilizar el seguimiento en tiempo real de Varify

    Paso 1: Configura tu sistema de seguimiento

    Ve a „Tracking Setup“ y selecciona „Varify Realtime Tracking“. A continuación, puedes decidir si quieres integrar el seguimiento de Varify con o sin „consentimiento del usuario“. Esta página te puede ayudar a tomar la decisión: https://varify.io/en/varify-tracking-gdpr-compliance/

    Paso 2: Activación del seguimiento en tiempo real de Varify

    Integración sin el consentimiento del usuario

    Si has decidido cargar el seguimiento en tiempo real de Varify sin el consentimiento del usuario, la configuración básica del seguimiento ya está lista y puedes continuar con el paso 3. 

    Integración con el consentimiento del usuario

    Para la integración con el consentimiento del usuario, se te proporciona un script de activación. En cuanto se carga este script, se activa el seguimiento. Integra el script siguiendo las instrucciones de tu plataforma de gestión del consentimiento (CSP). También se puede utilizar la integración a través de GTM mediante la visita a la página y una señal de consentimiento válida.

    Paso 3: Configuración de tus objetivos de Varify

    Para evaluar tus experimentos, debes crear objetivos. Encontrarás la sección «Objetivos» en la barra lateral de la izquierda.

    En la sección «Objetivos», puedes elegir entre los tres tipos de objetivos: «Objetivo de clic», «Objetivo de visita a la página» y «Objetivo personalizado».

    Configuración de los objetivos de clic

    Con Click Goals puedes medir la frecuencia con la que los participantes en el experimento hacen clic en los elementos. Para ello, debes definir un selector específico para cada elemento. Para identificar un selector específico para un elemento, puedes utilizar, por ejemplo, el editor visual de Varify. Ábrelo y selecciona el elemento que desees medir. A continuación, copia el selector e introdúcelo en el Click Goal, en el campo «Selector».

    Configuración de objetivos de visitas a la página

    Con los objetivos de visitas a la página, puedes medir la frecuencia con la que los participantes en tu experimento ven determinadas páginas o tipos de páginas. Para ello, solo tienes que definir tus propias reglas para medir así las visitas a las páginas según tu definición.

    Configuración de objetivos personalizados

    Con la ayuda de los objetivos personalizados, puedes crear objetivos individuales. Esto resulta útil, por ejemplo, si quieres medir el valor de los ingresos de tu pedido. Los objetivos personalizados se activan mediante JavaScript en tu experimento. 

    Recuento de conversiones

    Con esta configuración, se determina si un objetivo debe medirse una sola vez o varias veces. Por defecto, en las pruebas A/B cada objetivo se mide solo una vez, ya que normalmente lo único que interesa es saber si un usuario ha alcanzado el objetivo. Por el contrario, la medición múltiple puede resultar útil en el caso de los objetivos de ingresos: en este caso, el valor del pedido de un usuario puede acumularse a lo largo de varias sesiones.

    Seguimiento de los objetivos de pago de Shopify

    Los objetivos de eventos de Shopify, como „Compra“ o „Iniciar pago“, no se pueden medir directamente a través de tu página web. Desde 2025, Shopify ha establecido como obligatorio que estos solo se puedan medir a través del píxel personalizado en tu página web. Para poder medir estos objetivos, ve a „Configuración > Eventos de cliente“ y haz clic en el botón „Añadir píxel personalizado“. Asigna al píxel el nombre „Varify Realtime Tracking“ y haz clic en «Añadir píxel».

    Una vez que hayas creado el píxel, abre primero la sección „Privacidad del cliente“ y, en «Venta de datos», selecciona la opción «Los datos recopilados no se consideran venta de datos». Varify no vende datos y solo realiza un seguimiento de valores agregados para el análisis de tus experimentos.

    En „Permisión“, debes seleccionar „No es necesario“ si deseas realizar el seguimiento sin el consentimiento del usuario, tal y como se ha establecido anteriormente en la configuración del seguimiento. Sin embargo, si realizas el seguimiento con el consentimiento del usuario, puedes dejar el ajuste en «Necesario».

    A continuación, copia el código que aparece resaltado aquí, debajo del párrafo, y pégalo en la sección «Código». 

    				
    					/**
     * Varify Shopify Web Pixel — links experiments/variations a customer saw to
     * whether they completed a purchase.
     */
    
    let storage;
    
    // Store's currency, used to normalize order totals before sending them.
    const SHOP_CURRENCY = init.data?.shop?.paymentSettings?.currencyCode || 'EUR';
    
    // Free FX API, used only when an order's currency differs from the store's.
    const FX_API = 'https://api.frankfurter.dev/v1/latest';
    const FX_TTL_MS = 6 * 60 * 60 * 1000;
    const FX_TIMEOUT_MS = 2000;
    
    const VARIFY_INGESTION_ENDPOINT = 'https://w.varify.io/events';
    
    // One id per pixel session, reused for every event this pixel sends.
    const pixelSessionId = crypto.randomUUID();
    
    // Reads this pixel's saved data (experiments seen, anonymous id, etc.),
    // cached in-memory after the first read.
    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) {
          // Don't cache a broken read — retry from scratch next time.
          storage = undefined;
          console.error('Varify: error reading local storage', error);
          return {};
        }
      })();
      return storage;
    }
    
    // Units of `currency` per 1 unit of the store's currency, or null if
    // unavailable — callers should skip sending rather than guess a rate.
    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) {
        // Fall through and 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;
      }
    }
    
    // Varify's endpoint only accepts flat text/number values, so a list of
    // experiments becomes two comma-separated strings (ids and variation ids).
    function flattenExperiments(entries) {
      return {
        experiment_ids: entries.map((entry) => entry.experimentId).join(','),
        variation_ids: entries
          .map((entry) =>
            entry.variationId === null ? 'original' : entry.variationId,
          )
          .join(','),
      };
    }
    
    // Edge's identifier also contains "Chrome/", so it must be checked first.
    function detectBrowser(userAgent) {
      if (userAgent.includes('Firefox/')) return 'Firefox';
      if (userAgent.includes('Edg/')) return 'Edge';
      if (userAgent.includes('Chrome/')) return 'Chrome';
      if (userAgent.includes('Safari/')) return 'Safari';
      return 'Unknown';
    }
    
    function detectDevice(userAgent) {
      if (/Tablet|iPad/i.test(userAgent)) return 'tablet';
      if (/Mobi|Android/i.test(userAgent)) return 'mobile';
      return 'desktop';
    }
    
    function detectOs(userAgent) {
      if (/iPhone|iPad|iPod/i.test(userAgent)) return 'iOS';
      if (userAgent.includes('Android')) return 'Android';
      if (userAgent.includes('Win')) return 'Windows';
      if (userAgent.includes('Mac')) return 'macOS';
      if (userAgent.includes('Linux')) return 'Linux';
      return 'Unknown';
    }
    
    // Browser timezone, e.g. "Europe/Berlin" — not GPS or IP-based location.
    function detectLocation() {
      try {
        return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;
      } catch {
        return undefined;
      }
    }
    
    // Non-identifying browser/page details, added to every event. The pixel
    // sandbox may not expose all of these — each lookup is best-effort and
    // simply omitted if unavailable.
    function collectBrowserInfo() {
      const info = {};
    
      try {
        const userAgent =
          typeof navigator !== 'undefined' ? navigator.userAgent : undefined;
        if (typeof userAgent === 'string') {
          info.userAgent = userAgent;
          info.browser = detectBrowser(userAgent);
          info.device = detectDevice(userAgent);
          info.os = detectOs(userAgent);
        }
      } catch (error) {
        // Not available in this environment.
      }
    
      try {
        if (typeof window !== 'undefined' && window.location) {
          info.path = window.location.pathname;
          info.domain = window.location.hostname;
        }
      } catch (error) {
        // Not available in this environment.
      }
    
      const location = detectLocation();
      if (location) {
        info.location = location;
      }
    
      return info;
    }
    
    // Sends one event to Varify. Never throws — failures are logged and
    // swallowed so the pixel/checkout is never affected. Falls back to reading
    // the anonymous id from local storage if one wasn't passed in, and returns
    // whichever id was actually used so the caller can persist it.
    async function sendVarifyEvent(name, teamSqid, anonymousId, properties) {
      const anonymousIdWithFallback =
        anonymousId || localStorage.getItem('varify_anonymous_id');
    
      if (!teamSqid || !anonymousIdWithFallback) {
        console.warn(
          `Varify: skipping "${name}" event — missing teamSqid or anonymousId.`,
          'Team Sqid',
          teamSqid,
          'Anonymous ID',
          anonymousIdWithFallback,
        );
        return anonymousIdWithFallback;
      }
    
      try {
        await fetch(VARIFY_INGESTION_ENDPOINT, {
          method: 'POST',
          keepalive: true,
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            events: [
              {
                id: crypto.randomUUID(),
                name,
                teamId: String(teamSqid),
                anonymousId: anonymousIdWithFallback,
                sessionId: pixelSessionId,
                properties: { ...collectBrowserInfo(), ...properties },
              },
            ],
          }),
        });
      } catch (error) {
        console.error(`Varify: error sending "${name}" event`, error);
      }
    
      return anonymousIdWithFallback;
    }
    
    // Fired by Varify's tracking script when a customer is shown an experiment.
    // Remembers it locally so it can be attached to a later checkout event.
    analytics.subscribe('varify', async (event) => {
      const {
        varify_experimentId,
        varify_variationId,
        varify_anonymousId,
        teamId,
        teamSqid,
        storageType = 'localStorage',
      } = event.customData || {};
    
      if (!varify_experimentId || !teamId || !teamSqid) {
        console.warn('Varify: event missing experimentId or teamId. Event:', event);
        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.teamSqid = teamSqid;
      existingData.storageType = storageType;
    
      // Only present once Varify's tracking script has finished loading —
      // don't overwrite a previously-saved id with nothing.
      if (varify_anonymousId) {
        existingData.anonymousId = varify_anonymousId;
      }
    
      await browser.localStorage.setItem(
        'varify-data',
        JSON.stringify(existingData),
      );
    });
    
    // How long an experiment exposure counts toward a later purchase.
    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),
      );
    
      if (existingData.data.length > 0) {
        const resolvedAnonymousId = await sendVarifyEvent(
          'checkout_started',
          existingData.teamSqid,
          existingData.anonymousId,
          flattenExperiments(existingData.data),
        );
    
        // Persist the fallback id, if used, so it doesn't need re-deriving.
        if (
          resolvedAnonymousId &&
          resolvedAnonymousId !== existingData.anonymousId
        ) {
          existingData.anonymousId = resolvedAnonymousId;
          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 and the event carries no rate or
      // shop-money amount. Convert to shop currency 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 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);
      }
    
      const resolvedAnonymousId = await sendVarifyEvent(
        'checkout_completed',
        parsedData.teamSqid,
        parsedData.anonymousId,
        {
          ...flattenExperiments(parsedData.data),
          order_id: String(orderId),
          value: orderRevenue,
          currency: SHOP_CURRENCY,
          cart_product_ids: cartProducts.map((p) => p.productId).join(','),
          cart_variant_ids: cartProducts.map((p) => p.variantId).join(','),
        },
      );
    
      // Persist the fallback id, if used, so it doesn't need re-deriving.
      if (resolvedAnonymousId) {
        parsedData.anonymousId = resolvedAnonymousId;
      }
    
      parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
      await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
    });
    
    				
    			

    Una vez que hayas añadido el código, haz clic en «Conectar» en el menú superior. A continuación, el seguimiento de Shopify ya estará configurado.

  • Primeros pasos