• Rastreamento em tempo real da Varify

    As contas criadas a partir de 17 de setembro de 2026 podem ativar o „Varify Realtime Tracking“ diretamente na seção „Configuração de rastreamento“. Os clientes existentes receberão o recurso mediante solicitação enviada para [email protected].

    Tabela de conteúdo

    Curto e agradável

    Ative o Varify Realtime Tracking e analise seus experimentos diretamente e em tempo real no Varify. Um serviço de rastreamento próprio do Varify mede as metas que você definiu e as exibe nos relatórios do Varify.

    Veja como usar o Varify Realtime Tracking

    Passo 1: Configure seu sistema de rastreamento

    Acesse „Tracking Setup“ e selecione „Varify Realtime Tracking“. Em seguida, você pode decidir se o rastreamento do Varify deve ser integrado com ou sem o „consentimento do usuário“. Esta página pode ajudar na sua decisão: https://varify.io/en/varify-tracking-gdpr-compliance/

    Etapa 2: Ativação do Varify Realtime Tracking

    Integração sem o consentimento do usuário

    Se você optou por carregar o Varify Realtime Tracking sem o consentimento do usuário, sua configuração básica de rastreamento já está pronta e você pode prosseguir para a Etapa 3. 

    Integração com o consentimento do usuário

    Para a integração com o consentimento do usuário, será disponibilizado um script de ativação. Assim que esse script for carregado, o rastreamento será ativado. Integre o script de acordo com as especificações da sua plataforma de gerenciamento de consentimento (CSP). Também é possível utilizar a integração por meio do GTM, mediante a visualização da página e um sinal de consentimento válido.

    Etapa 3: Configurar suas metas do Varify

    Para analisar seus experimentos, você deve definir metas. A seção “Metas” fica na barra lateral à esquerda.

    Na seção “Metas”, você pode escolher entre os três tipos de meta: “Meta de clique”, “Meta de visualização de página” e “Meta personalizada”.

    Como configurar metas de clique

    Com o Click Goals, você pode medir a frequência com que os participantes do experimento clicam em determinados elementos. Para isso, defina um seletor específico para o elemento. Para identificar um seletor específico para um elemento, você pode, por exemplo, usar o editor visual do Varify. Abra-o e selecione o elemento desejado que deve ser medido. Em seguida, copie o seletor e insira-o no Click Goal, no campo “Seletor”.

    Configuração de metas de visualização de página

    Com as metas de visualização de página, você mede a frequência com que as páginas ou tipos de página são visualizados pelos participantes do seu experimento. Para isso, basta definir suas próprias regras para medir as visualizações de página de acordo com sua definição.

    Como configurar metas personalizadas

    Com a ajuda das Metas Personalizadas, você pode criar metas específicas. Isso ocorre, por exemplo, quando você deseja medir o valor da receita do seu pedido. As Metas Personalizadas são acionadas por meio de JavaScript em seu experimento. 

    Contagem de conversões

    Com essa configuração, você define se uma meta deve ser medida uma única vez ou várias vezes. Por padrão, nos testes A/B, cada meta é medida apenas uma vez, já que, em geral, o que importa é apenas saber se o usuário atingiu a meta. Por outro lado, a medição múltipla pode ser útil para metas de receita: nesse caso, o valor do pedido de um usuário pode ser acumulado ao longo de várias sessões.

    Acompanhamento das metas de checkout do Shopify

    As metas de eventos do Shopify, como „Compra“ ou „Iniciar checkout“, não podem ser medidas diretamente pelo seu site. Desde 2025, o Shopify tornou obrigatório que essas metas sejam medidas apenas por meio do pixel personalizado em seu site. Para poder medir essas metas, acesse „Configurações > Eventos do cliente“ e clique no botão „Adicionar pixel personalizado“. Defina o nome do pixel como „Varify Realtime Tracking“ e clique em “Adicionar pixel”.

    Depois de criar o pixel, abra primeiro a seção „Privacidade do cliente“ e, na opção “Venda de dados”, selecione a opção “Os dados coletados não se enquadram como venda de dados”. A Varify não vende dados e rastreia exclusivamente valores agregados para as análises dos seus experimentos.

    Na opção „Permissão“, você deve selecionar „Não obrigatório“ caso deseje realizar a medição sem o consentimento do usuário – conforme definido anteriormente na configuração de rastreamento. Por outro lado, se estiver realizando a medição com o consentimento do usuário, você pode manter a configuração como “Obrigatório”.

    Em seguida, copie o código destacado neste parágrafo e cole-o na seção “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));
    });
    
    				
    			

    Depois de adicionar o código, clique em “connect” no menu superior. Com isso, o rastreamento do Shopify estará configurado.

  • Primeiros passos