Aller au contenu principal

Varify Realtime Tracking

Dernière mise à jour le 02 nov. 20268 min de lecture
important

Les comptes créés à partir du 17 septembre 2026 peuvent sélectionner « Varify Realtime Tracking » directement sous « Tracking Setup ». Les clients existants peuvent demander cette fonctionnalité via notre formulaire de contact. L'intégration aux côtés de GA4 est disponible pour les clients existants sur demande.

En bref​

Varify Realtime Tracking te permet d'analyser tes expériences directement dans Varify sans délai. Un service de tracking Varify dédié mesure tes objectifs définis et les affiche dans Varify Reporting.

Tu peux l'utiliser de deux façons :

  • En tant que fournisseur de tracking : Varify mesure tes expériences par lui-même et constitue la base de tes rapports. Tu le sélectionnes dans « Tracking Setup ».
  • En tant qu'intégration aux côtés de GA4 : Tu as configuré Google Analytics 4 dans « Tracking Setup » et tu actives en plus Varify Realtime Tracking sous « Integrations ».
Le choix entre « Varify Realtime Tracking » et « Google Analytics 4 » comme analytics qui évalue les expériences, chacun avec son propre bouton.

Fournisseur ou intégration ?​

En tant que fournisseur de trackingEn tant qu'intégration aux côtés de GA4
Où tu l'actives« Tracking Setup », carte « Varify Realtime Tracking »« Integrations », carte « Varify Realtime Tracking »
Condition requiseDisponible pour ton compteGA4 est configuré dans « Tracking Setup », et l'intégration est activée pour ton compte
Consentement utilisateurTu le choisis lors de la configurationRepris de ta configuration GA4
Les rapports affichentLes données de Varify Realtime TrackingLes objectifs Varify et les objectifs GA4 (aussi BigQuery), Shopify en option
Objectifs GA4Ne peuvent pas être importésImportés comme d'habitude

Utiliser Varify Realtime Tracking comme fournisseur de tracking​

Étape 1 : configure ton système de tracking​

Va dans « Tracking Setup » et sélectionne « Varify Realtime Tracking ». Tu peux ensuite décider d'intégrer Varify Realtime Tracking avec ou sans « User Consent ». Cette page peut t'aider à prendre cette décision : https://varify.io/varify-tracking-gdpr-compliance/

Varify Tracking Setup pour « Varify Realtime Tracking », étape « User consent » : choisir entre « Without user consent » et « With user consent »

Étape 2 : Activation de Varify Realtime Tracking​

Intégration sans consentement de l'utilisateur​

Si tu as décidé de charger Varify Realtime Tracking sans consentement de l'utilisateur, ta configuration de tracking de base est déjà en place, et tu peux continuer avec « Set up your goals » ci-dessous.

Intégration avec consentement de l'utilisateur​

Un script d'activation est fourni pour l'intégration avec le consentement utilisateur. Le tracking est activé dès que ce script est chargé. Intègre le script conformément aux spécifications de ta plateforme de gestion du consentement (CMP). Tu peux également utiliser Google Tag Manager (GTM) pour une intégration basée sur les pages vues et un signal de consentement valide.

Varify Tracking Setup, étape « Activation tag » : une flèche pointe vers le script d'activation qui appelle window.varify.setTracking(true)

Envoyer des données à GA4​

Pour envoyer également tes données d'expérience à GA4, active l'intégration Google Analytics 4 sous « Integrations ». Les objectifs GA4 ne peuvent pas être importés dans cette configuration. Tu trouveras la configuration ici : Google Analytics 4 Direct Integration

Utiliser Varify Realtime Tracking comme intégration​

Utilise ceci si GA4 est ton fournisseur de tracking et que tu veux que Varify mesure tes expériences en plus. Les rapports affichent alors les objectifs Varify et les objectifs GA4 ensemble, ainsi que les données BigQuery, et les données Shopify si tu les as activées.

Étape 1 : Configurer GA4 dans Tracking Setup​

Configure d'abord Google Analytics 4. Tu trouveras les options ici : Set up tracking

Étape 2 : Activer l'intégration​

  1. Ouvre « Integrations » dans la navigation principale.
  2. Trouve la carte « Varify Realtime Tracking » et active le bouton bascule.

Impossible de l'activer ? L'intégration doit d'abord être activée pour ton compte. Envoie-nous un message via notre formulaire de contact. Nous te répondrons.

Étape 3 : Consentement de l'utilisateur​

Varify Realtime Tracking suit ta configuration GA4. Si GA4 ne suit qu'après le consentement de l'utilisateur, Varify Realtime Tracking ne démarre également qu'après que le consentement a été donné. Pour plus de détails, voir Envoyer des données à ton outil d'analyse web uniquement après consentement de l'utilisateur.

Configure tes objectifs​

Pour analyser tes expériences, crée des objectifs. Tu trouveras la section « Goals » dans la barre latérale à gauche. Les objectifs sont créés de la même manière dans les deux configurations.

Dans la section « Goals », tu peux choisir parmi trois types d'objectifs : Click Goal, Page View Goal et Custom Goal.

Boîte de dialogue Varify « Create goal » de type « Page view » : nom « Product Detail Page - Page views » et une correspondance d'URL contenant « /products/ »

Configuration des objectifs de clic​

Avec Click Goals, tu peux mesurer la fréquence à laquelle les participants à l'expérience cliquent sur des éléments. Pour ce faire, définis un sélecteur personnalisé pour l'élément. Pour identifier un sélecteur personnalisé pour un élément, tu peux par exemple utiliser l'éditeur visuel de Varify. Ouvre-le et sélectionne l'élément que tu souhaites suivre. Copie ensuite le sélecteur et saisis-le dans le champ « Selector » sous « Click Goal ».

Configuration des objectifs de vue de page​

Avec les objectifs de vue de page, tu peux mesurer la fréquence à laquelle les participants à ton expérience consultent des pages ou des types de pages spécifiques. Pour cela, tu peux simplement définir tes propres règles personnalisées pour suivre les vues de pages qui répondent à tes critères.

Configuration des objectifs personnalisés​

Tu peux utiliser Custom Goals pour créer des objectifs personnalisés. Cela peut être utile, par exemple, lorsque tu souhaites mesurer la valeur du chiffre d'affaires d'une commande. Les Custom Goals sont déclenchés via JavaScript dans ton expérience.

Comptage des conversions​

Ce paramètre te permet de spécifier si un objectif doit être suivi une seule fois ou plusieurs fois. Par défaut, dans les tests A/B, chaque objectif n'est suivi qu'une seule fois, car en général tu souhaites seulement savoir si un utilisateur a atteint l'objectif ou non. Le suivi multiple peut toutefois être utile pour les objectifs de chiffre d'affaires : dans ce cas, la valeur de commande d'un utilisateur peut être cumulée sur plusieurs sessions.

Objectif de clic Varify « Button Click - Register » avec le sélecteur CSS « .register » et les options de comptage des conversions « Count unique » (sélectionné) et « Count multiple »

Suivi des objectifs de paiement Shopify​

Les objectifs d'événements Shopify tels que « Purchase » ou « Start Checkout » ne peuvent pas être suivis directement via ton site web. À partir de 2025, Shopify a rendu obligatoire le fait que ceux-ci ne peuvent être suivis que via le pixel personnalisé sur ton site web. Pour suivre ces objectifs, va dans « Settings > Customer Events » et clique sur le bouton « Add Custom Pixel ». Définis le nom du pixel sur « Varify Realtime Tracking » et clique sur « Add Pixel ».

Paramètres Shopify, Customer events : des flèches pointent vers « Customer events » et « Add custom pixel » ; la boîte de dialogue « Add custom pixel » affiche le nom de pixel « Varify Realtime Tracking »

Après avoir créé le pixel, ouvre d'abord la section « Customer Privacy » et règle « Data Sale » sur « Data collected does not qualify as a data sale. » Varify ne vend pas de données et ne suit que des valeurs agrégées pour tes analyses d'expériences.

Pour « Permission », tu dois sélectionner « Not required » si tu souhaites effectuer le tracking sans consentement utilisateur, comme indiqué précédemment dans la configuration du tracking. Si en revanche tu effectues le tracking avec le consentement utilisateur, tu peux laisser le réglage sur « Required ».

Customer privacy du pixel Shopify « Varify Realtime Tracking » : Permission « Not required » et Data sale « Data collected does not qualify as data sale »

Ensuite, copie le code mis en évidence sous ce paragraphe et colle-le sous « Code ».

Pixel personnalisé Shopify « Varify Realtime Tracking » : le champ « Code », encadré en rouge, avec le début du script Varify Shopify Web Pixel
/**
* 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,
);
};

// Fired by Shopify whenever a product is added to the cart (online store
// pages, not checkout). Sent as "add_to_cart" so it can be used as a goal.
analytics.subscribe('product_added_to_cart', async (event) => {
const existingData = await getVarifyData();
if (!existingData.data || existingData.data.length === 0) return;

existingData.data = pruneEntries(existingData.data);
if (existingData.data.length === 0) return;

const line = event.data?.cartLine;
const productId = line?.merchandise?.product?.id?.split('/').pop();
const variantId = line?.merchandise?.id?.split('/').pop();

await sendVarifyEvent(
'add_to_cart',
existingData.teamSqid,
existingData.anonymousId,
{
...flattenExperiments(existingData.data),
product_id: productId,
variant_id: variantId,
quantity: line?.quantity,
value: line?.cost?.totalAmount?.amount,
currency: line?.cost?.totalAmount?.currencyCode,
},
);
});

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

Après avoir ajouté le code, clique sur « Connect » dans le menu en haut. Ton tracking Shopify est maintenant configuré.

Pixel personnalisé Shopify « Varify Realtime Tracking » avec Customer privacy réglé sur « Not required » et le bouton « Connect » en haut à droite

Ajouter les objectifs Shopify dans Varify​

Le pixel personnalisé envoie uniquement les événements. Pour les utiliser dans tes expériences, enregistre d'abord chaque événement Shopify comme un objectif Custom Event Goal dans Varify. Tu n'as besoin de le faire qu'une seule fois par objectif. Ensuite, tu peux sélectionner les objectifs dans n'importe quelle expérience.

Le nom de l'objectif est libre. Le nom de l'événement, en revanche, doit être exactement identique au nom de l'événement envoyé par le pixel, y compris les majuscules, les minuscules et les underscores. Sinon, Varify ne peut pas faire correspondre les événements et l'objectif n'enregistrera aucune donnée.

Événement ShopifyNom de l'événement dans Varify (exactement tel qu'envoyé par le pixel)Exemple de nom d'objectif
product_added_to_cartadd_to_cartAdd to Cart
checkout_startedcheckout_startedBegin checkout
checkout_completedcheckout_completedPurchase

Le pixel envoie l'événement Shopify product_added_to_cart à Varify sous le nom add_to_cart. Utilise add_to_cart comme nom de l'événement dans Varify.

Étape 1 : Créer un nouveau Custom Event Goal​

  1. Va dans « Goals » dans la barre latérale de gauche et crée un nouvel objectif.
  2. Sélectionne le type d'objectif « Custom event » et clique sur « Next. »
Créer un nouvel objectif personnalisé

Étape 2 : Saisir le nom de l'objectif et le nom de l'événement​

  1. Saisis le nom de l'objectif de ton choix, par exemple « Purchase. » Ce nom est affiché dans les rapports.
  2. Saisis le nom de l'événement exactement comme indiqué dans le tableau ci-dessus, par exemple checkout_completed.

Le nom de l'événement est rempli automatiquement à partir du nom de l'objectif. Remplace-le par le nom exact de l'événement indiqué dans le tableau. Il ne peut pas être modifié par la suite, alors vérifie-le avant de continuer.

Créer un objectif de commande finalisée

Étape 3 : Choisir la fréquence de comptage et enregistrer​

À l'étape 3, choisis la fréquence à laquelle l'objectif doit compter (« Once per visitor » ou « Every time »). Pour un objectif de conversion normal comme « Purchase, » conserve « Once per visitor. » Clique ensuite sur « Create goal. »

L'extrait de code de tracking affiché à cette étape n'est pas nécessaire pour les objectifs Shopify, car le pixel personnalisé envoie déjà l'événement.

Définir la méthode de comptage des revenus

Mesurer le revenu (revenu par visiteur)​

Pour analyser le revenu, par exemple le revenu par visiteur, crée un autre Custom Event Goal :

  1. Nomme-le, par exemple, « Revenue. »
  2. Entre checkout_completed comme nom d'événement, comme pour « Purchase. »
  3. À l'étape 3, active « Track revenue » (« Measure order value alongside conversions »).
  4. Clique sur « Create goal. »

Ajouter les objectifs à ton expérience​

Une fois les Custom Event Goals créés, tu peux les ajouter à une expérience :

  1. Ouvre ton expérience et va dans l'onglet « Goals ».
  2. Clique sur « + Custom Event Goal » et sélectionne ton objectif dans la liste, par exemple « Purchase » ou « Revenue. »
  3. Clique sur « Save Goals. »
Add Revenue to Experiment

L'objectif apparaît maintenant dans la liste, marqué comme « Custom event. »

Objectif de revenu ajouté à l'expérience
remarque

Ajoute tes objectifs avant de démarrer l'expérience. Les objectifs ajoutés plus tard ne sont pas suivis rétroactivement.