Zum Hauptinhalt springen

Varify Realtime Tracking

Zuletzt aktualisiert am 09. Okt. 20263 Min. Lesezeit
important

Konten, die am oder nach dem 17. September 2026 erstellt wurden, können „Varify Realtime Tracking“ direkt unter „Tracking Setup“ aktivieren. Bestandskunden können diese Funktion anfragen, indem sie [email protected] kontaktieren.

Kurz gefasst​

Aktiviere Varify Realtime Tracking und analysiere deine Experimente direkt in Varify ohne Verzögerung. Ein dedizierter Varify-Tracking-Dienst misst deine festgelegten Ziele und zeigt sie im Varify Reporting an.

Die Wahl zwischen „Varify Realtime Tracking“ und „Google Analytics 4“ als Analytics, das die Experimente auswertet, jeweils mit eigenem Button.

So verwendest du Varify Realtime Tracking​

Schritt 1: Richte dein Tracking-System ein​

Gehe zu „Tracking Setup“ und wähle „Varify Realtime Tracking“. Anschließend kannst du entscheiden, ob du Varify Realtime Tracking mit oder ohne „User Consent“ integrieren möchtest. Diese Seite kann dir bei dieser Entscheidung helfen: https://varify.io/varify-tracking-gdpr-compliance/

Varify Tracking Setup für „Varify Realtime Tracking“, Schritt „User consent“: Wahl zwischen „Without user consent“ und „With user consent“

Schritt 2: Varify Realtime Tracking aktivieren​

Wenn du dich entschieden hast, Varify Realtime Tracking ohne User Consent zu laden, ist dein grundlegendes Tracking-Setup bereits vorhanden, und du kannst mit Schritt 3 fortfahren.

Für die Integration mit der Nutzereinwilligung wird ein Aktivierungsskript bereitgestellt. Das Tracking wird aktiviert, sobald dieses Skript geladen wird. Integriere das Skript gemäß den Vorgaben deiner Consent-Management-Plattform (CMP). Du kannst für die Integration basierend auf Seitenaufrufen und einem gültigen Einwilligungssignal auch Google Tag Manager (GTM) verwenden.

Varify Tracking Setup, Schritt „Activation tag“: Ein Pfeil zeigt auf das Aktivierungsskript, das window.varify.setTracking(true) aufruft

Schritt 3: Einrichten deiner Varify-Ziele​

Um deine Experimente zu analysieren, erstelle Ziele. Den Bereich „Goals“ findest du in der Seitenleiste links.

Im Bereich „Goals“ kannst du aus drei Zieltypen wählen: Click Goal, Page View Goal und Custom Goal.

Varify-Dialog „Create goal“ vom Typ „Page view“: Name „Product Detail Page - Page views“ und ein URL-Match, der „/products/“ enthält

Einrichten von Click Goals​

Mit Click Goals kannst du messen, wie oft Experiment-Teilnehmer auf Elemente klicken. Definiere dazu einen benutzerdefinierten Selektor für das Element. Um einen benutzerdefinierten Selektor für ein Element zu ermitteln, kannst du zum Beispiel den visuellen Editor von Varify verwenden. Öffne ihn und wähle das Element aus, das du tracken möchtest. Kopiere anschließend den Selektor und gib ihn im Feld „Selector“ unter „Click Goal“ ein.

Einrichten von Page View Goals​

Mit Page View Goals kannst du messen, wie oft deine Experiment-Teilnehmer bestimmte Seiten oder Seitentypen ansehen. Dazu kannst du einfach eigene, benutzerdefinierte Regeln definieren, um die Seitenaufrufe zu tracken, die deinen Kriterien entsprechen.

Einrichten von Custom Goals​

Mit Custom Goals kannst du benutzerdefinierte Ziele erstellen. Das kann zum Beispiel der Fall sein, wenn du den Umsatzwert einer Bestellung messen möchtest. Custom Goals werden über JavaScript in deinem Experiment ausgelöst.

Conversion-Zählung​

Mit dieser Einstellung kannst du festlegen, ob ein Ziel einmal oder mehrfach getrackt werden soll. Standardmäßig wird beim A/B-Testing jedes Ziel nur einmal getrackt, da in der Regel nur interessiert, ob ein Nutzer das Ziel überhaupt erreicht hat. Mehrfaches Tracking kann jedoch bei Umsatzzielen sinnvoll sein: In diesem Fall kann der Bestellwert eines Nutzers über mehrere Sitzungen hinweg akkumuliert werden.

Varify Click Goal „Button Click - Register“ mit dem CSS-Selektor „.register“ und den Conversion-Counting-Optionen „Count unique“ (ausgewählt) und „Count multiple“

Tracking von Shopify Checkout Goals​

Shopify-Event-Goals wie „Purchase“ oder „Start Checkout“ können nicht direkt über deine Website getrackt werden. Ab 2025 schreibt Shopify vor, dass diese nur über den Custom Pixel auf deiner Website getrackt werden können. Um diese Goals zu tracken, gehe zu „Settings > Customer Events“ und klicke auf die Schaltfläche „Add Custom Pixel“. Setze den Pixel-Namen auf „Varify Realtime Tracking“ und klicke auf „Add Pixel“.

Shopify-Einstellungen, Customer events: Pfeile zeigen auf „Customer events“ und „Add custom pixel“; der Dialog „Add custom pixel“ hat den Pixel-Namen „Varify Realtime Tracking“

Nachdem du den Pixel erstellt hast, öffne zunächst den Bereich „Customer Privacy“ und setze „Data Sale“ auf „Data collected does not qualify as a data sale.“ Varify verkauft keine Daten und trackt nur aggregierte Werte für deine Experimentanalysen.

Bei „Permission“ solltest du „Not required“ auswählen, wenn du ohne Nutzereinwilligung tracken möchtest – wie zuvor im Tracking-Setup festgelegt. Wenn du hingegen mit Nutzereinwilligung trackst, kannst du die Einstellung auf „Required“ belassen.

Customer privacy des Shopify-Pixels „Varify Realtime Tracking“: Permission „Not required“ und Data sale „Data collected does not qualify as data sale“

Kopiere als Nächstes den unter diesem Absatz hervorgehobenen Code und füge ihn unter „Code“ ein.

Shopify Custom Pixel „Varify Realtime Tracking“: das rot umrandete Feld „Code“ mit dem Anfang des Varify Shopify Web Pixel Scripts
/**
* 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));
});

Nachdem du den Code hinzugefügt hast, klicke oben im Menü auf „Connect“. Dein Shopify-Tracking ist jetzt eingerichtet.

Shopify Custom Pixel „Varify Realtime Tracking“ mit Customer privacy auf „Not required“ gesetzt und dem Button „Connect“ oben rechts