• Suivi en temps réel Varify

    Les comptes créés depuis le 17 septembre 2026 peuvent activer la fonctionnalité „ Varify Realtime Tracking “ directement dans la rubrique „ Tracking Setup “. Les clients existants peuvent obtenir cette fonctionnalité sur simple demande adressée à [email protected].

    Table des matières

    En bref

    Activez le suivi en temps réel de Varify et analysez vos expériences directement et en temps réel dans Varify. Un service de suivi Varify dédié mesure les objectifs que vous avez définis et les affiche dans les rapports Varify.

    Comment utiliser le suivi en temps réel de Varify

    Étape 1 : Configurez votre système de suivi

    Rends-toi dans „ Tracking Setup “ et sélectionne „ Varify Realtime Tracking “. Tu peux ensuite choisir d'intégrer le suivi Varify avec ou sans „ consentement de l'utilisateur “. Cette page peut t'aider à prendre ta décision : https://varify.io/en/varify-tracking-gdpr-compliance/

    Étape 2 : Activation du suivi en temps réel Varify

    Intégration sans consentement de l'utilisateur

    Si tu as choisi de charger le suivi en temps réel Varify sans le consentement de l'utilisateur, ta configuration de suivi de base est déjà en place et tu peux passer à l'étape 3. 

    Intégration avec le consentement de l'utilisateur

    Pour l'intégration avec le consentement de l'utilisateur, un script d'activation est mis à ta disposition. Dès que ce script est chargé, le suivi est activé. Intègre le script conformément aux instructions de ta plateforme de gestion du consentement (CSP). Une intégration via GTM, à chaque consultation de page et en présence d'un signal de consentement valide, peut également être utilisée à cette fin.

    Étape 3 : Configurer vos objectifs Varify

    Pour analyser tes expériences, tu dois créer des objectifs. La section « Objectifs » se trouve dans la barre latérale à gauche.

    Dans la section « Objectifs », tu peux choisir parmi les 3 types d'objectifs suivants : « Objectif de clic », « Objectif de page vue » et « Objectif personnalisé ».

    Configuration des objectifs de clic

    Avec Click Goals, vous pouvez mesurer la fréquence à laquelle les participants à l'expérience cliquent sur des éléments. Pour cela, vous devez définir un sélecteur spécifique pour l'élément concerné. Pour identifier un sélecteur spécifique pour un élément, vous pouvez par exemple utiliser l'éditeur visuel de Varify. Ouvrez-le et sélectionnez l’élément que vous souhaitez mesurer. Copiez ensuite le sélecteur et collez-le dans le champ « Sélecteur » de l’objectif de clic.

    Configuration des objectifs de consultation de page

    Les objectifs de consultation de page te permettent de mesurer la fréquence à laquelle tes participants à l'expérience consultent des pages ou des types de pages. Pour cela, il te suffit de définir tes propres règles afin de mesurer les consultations de page selon ta définition.

    Configuration des objectifs personnalisés

    Les « objectifs personnalisés » te permettent de créer des objectifs sur mesure. C'est le cas, par exemple, si tu souhaites mesurer le chiffre d'affaires généré par ta commande. Les objectifs personnalisés sont déclenchés via JavaScript dans ton expérience. 

    Comptage des conversions

    Ce paramètre vous permet de définir si un objectif doit être mesuré une seule fois ou plusieurs fois. Par défaut, lors d'un test A/B, chaque objectif n'est mesuré qu'une seule fois, car on s'intéresse généralement uniquement à savoir si un utilisateur a atteint l'objectif ou non. Une mesure multiple peut en revanche s’avérer utile pour les objectifs de chiffre d’affaires : dans ce cas, le montant de la commande d’un utilisateur peut être cumulé sur plusieurs sessions.

    Suivi des objectifs de paiement Shopify

    Les objectifs d'événement Shopify tels que „ Achat “ ou „ Début du paiement “ ne peuvent pas être mesurés directement via votre site web. Depuis 2025, Shopify impose que ces événements ne puissent être mesurés que via le pixel personnalisé sur votre site web. Pour pouvoir désormais mesurer ces objectifs, rendez-vous dans „ Paramètres > Événements client “ et cliquez sur le bouton „ Ajouter un pixel personnalisé “. Donnez au pixel le nom „ Varify Realtime Tracking “, puis cliquez sur « Ajouter un pixel ».

    Une fois le pixel créé, ouvrez tout d’abord la section „ Confidentialité des clients “ et, dans la rubrique « Vente de données », sélectionnez l’option « Les données collectées ne constituent pas une vente de données ». Varify ne vend aucune donnée et ne suit que des valeurs agrégées pour l’analyse de vos expériences.

    Dans la section „ Permission “, tu dois sélectionner „ Not required “ si tu souhaites effectuer des mesures sans le consentement de l'utilisateur, comme défini plus haut dans la configuration du suivi. En revanche, si tu effectues des mesures avec le consentement de l'utilisateur, tu peux laisser le paramètre sur « Required ».

    Copiez ensuite le code surligné ici sous ce paragraphe et collez-le dans la section « Code ». 

    				
    					/**
     * 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));
    });
    
    				
    			

    Une fois le code ajouté, clique sur « connect » dans le menu en haut de la page. Ton suivi Shopify est alors configuré.

  • Premiers pas