Shopify Tracking
Inhaltsverzeichnis
Kurz & Knapp
Verbinde Shopify direkt mit Varify und analysiere deine Experimente direkt mit den Conversion und Revenue Daten aus Shopify. Die Integration funktioniert nur gemeinsam mit dem Einsatz von GA4, da die Anzahl der Visitor aus GA4 importiert werden.
So nutzt du Shopify Tracking Daten
Schritt 1: Aktiviere den Shopify Datenimport
Gehe zum “Tracking Setup” und klicke auf den Tab “Advanced Setup”. Aktiviere hier die Einstellung “Use Shopify Data”.
Schritt 2: Erstelle ein Custom Pixel mit Varify Tracking Code
Klicke auf “Settings” in deinem Shopify Store und öffne “Customer events”. Klicke danach auf “Add custom pixel” und nenne das Pixel: “varify-checkout”. Im Anschluss daran fügst du den nachfolgenden Code ein:
Standard Code
Nutze diesen Code, wenn in deinem Shopify Store mit nur einer Währung gezahlt werden kann.
let storage;
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) {
// Cache nicht dauerhaft vergiften: nach Fehler zuruecksetzen,
// damit der naechste Aufruf erneut versucht statt still tot zu bleiben.
storage = undefined;
console.error('Varify: error reading local storage', error);
return {};
}
})();
return storage;
}
analytics.subscribe('varify', async (event) => {
const {
varify_experimentId,
varify_variationId,
teamId,
storageType = 'localStorage',
} = event.customData || {};
if (!varify_experimentId || !teamId) {
console.warn('Varify: event missing experimentId or teamId.');
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.storageType = storageType;
await browser.localStorage.setItem(
'varify-data',
JSON.stringify(existingData)
);
});
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)
);
});
analytics.subscribe('checkout_completed', async (event) => {
const orderId = event.data?.checkout?.order?.id;
const orderRevenue = event.data?.checkout?.subtotalPrice?.amount;
// 0 is a valid revenue (100% discount) and must not count as missing.
if (!orderId || orderRevenue == null) {
console.warn('Varify: checkout_completed missing orderId or revenue.');
return;
}
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 storedAccountId = parsedData.teamId;
const experiments = parsedData.data.map((entry) => {
return [entry.experimentId, entry.variationId];
});
const storageType = parsedData.storageType;
const storeHandle = init.data?.shop?.myshopifyDomain?.replace('.myshopify.com', '');
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);
}
try {
await fetch('https://ecommerce.varify.io/store_data', {
method: 'POST',
keepalive: true,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
experiments,
orderId,
orderRevenue,
accountId: storedAccountId,
storageType: storageType,
storeHandle,
cartProducts,
}),
});
parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
} catch (error) {
console.error('Varify: error sending data', error);
}
});
Multi Währung Code
Nutze diesen Code, wenn in deinem Shopify Store mit mehreren Währung gezahlt werden kann.
let storage;
const SHOP_CURRENCY = init.data?.shop?.paymentSettings?.currencyCode || 'EUR';
const FX_API = 'https://api.frankfurter.dev/v1/latest';
const FX_TTL_MS = 6 * 60 * 60 * 1000;
const FX_TIMEOUT_MS = 2000;
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) {
// Reset so the next call retries instead of caching a poisoned promise.
storage = undefined;
console.error('Varify: error reading local storage', error);
return {};
}
})();
return storage;
}
// Returns units of per 1 , or null. null => do not send.
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) {
// Broken cache -> 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;
}
}
analytics.subscribe('varify', async (event) => {
const {
varify_experimentId,
varify_variationId,
teamId,
storageType = 'localStorage',
} = event.customData || {};
if (!varify_experimentId || !teamId) {
console.warn('Varify: event missing experimentId or teamId.');
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.storageType = storageType;
await browser.localStorage.setItem(
'varify-data',
JSON.stringify(existingData)
);
});
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)
);
// 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; the event carries no rate or shop-money
// amount, and /store_data books the number as shop currency. Convert 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 storedAccountId = parsedData.teamId;
const experiments = parsedData.data.map((entry) => {
return [entry.experimentId, entry.variationId];
});
const storageType = parsedData.storageType;
const storeHandle = init.data?.shop?.myshopifyDomain?.replace('.myshopify.com', '');
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);
}
try {
await fetch('https://ecommerce.varify.io/store_data', {
method: 'POST',
keepalive: true,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
experiments,
orderId,
orderRevenue,
// Already converted above; always shop currency so a future server-side
// conversion is a no-op, not a double conversion.
orderCurrency: SHOP_CURRENCY,
accountId: storedAccountId,
storageType: storageType,
storeHandle,
cartProducts,
}),
});
parsedData.sentOrders = [...parsedData.sentOrders, orderId].slice(-50);
await browser.localStorage.setItem('varify-data', JSON.stringify(parsedData));
} catch (error) {
console.error('Varify: error sending data', error);
}
});
Nachdem du den Code hinzugefügt hast, klicke auf speichern. Prüfe zusätzlich, dass deine Permissions korrekt gesetzt sind, ansonsten wird das Script nicht ausgeführt, falls eine User Permission fehlen sollte. Klicke im Anschluss auf “Connect”. Jetzt ist die Integration erfolgreich installiert.
Schritt 3: Füge deinem Experiment Shopify Goals hinzu
Stelle sicher, dass dein Tracking Setup eingerichtet ist und dass du GA4 als Tracking Provider ausgewählt hast und dass der Punkt “Test Evaluation” auf “In Varify and GA4” steht. Wenn dass der Fall ist, dann erscheinen ab jetzt für “neu angelegte” Experimente ein “Results Link” oben rechts am gestarteten Experiment.
Beim Klick auf den “Results Link” öffnet sich der entsprechende Ergebnisreport des Experiments. Nun kannst du per Klick auf “Add Shopify Goal” entweder “Conversions” oder “Revenue” als Goal hinzufügen. Die Visitors des Goals, werden aus GA4 übernommen. Der Conversion Wert und der Revenue Wert kommen direkt aus Shopify.
Wichtig: GA4 Daten werden erst nach ca. 24 Stunden upgedated. Die Daten aus Shopify werden in Echtzeit upgedated.
Produktfilter im Ergebnisreport
Analysiere die Performance bestimmter Produkte in deinem A/B‑Test. Du kannst entweder einzelne Produkte oder Produktvarianten betrachten. Gib dafür im Feld eine Produkt‑ID ein oder mehrere IDs, durch Kommata getrennt ein.