Saltar al contenido principal

Seguimiento de Shopify

Última actualización el 16 jul 20262 min de lectura

En resumen​

Conecta Shopify directamente con Varify y analiza tus experimentos directamente con los datos de conversión e ingresos de Shopify. La integración solo funciona junto con el uso de GA4, ya que el número de visitantes se importa desde GA4.

Evaluación de objetivos de Varify con dos objetivos: «Conversions» mejora un 9,49 % y es significativo, «Revenue» un 9,73 % con un 94,28 % de confianza y no lo es

Cómo usar los datos de seguimiento de Shopify​

Paso 1: Activa la importación de datos de Shopify​

Ve a „Tracking Setup“ y haz clic en la pestaña „Advanced Setup“. Aquí, activa el ajuste „Use Shopify Data“.

Configuración avanzada de Varify con „Start Tracking on Activation Event“ y „Use Shopify Data“ activados

Paso 2: Crea un pixel personalizado con el código de seguimiento de Varify​

Shopify «Customer events» con una flecha que señala el botón «Add custom pixel» encima de la lista de píxeles

Haz clic en "Settings" en tu tienda Shopify y abre "Customer events". Luego haz clic en "Add custom pixel" y nombra el píxel: "varify-checkout". A continuación, inserta el siguiente código:

Código estándar​

Usa este código si tu tienda Shopify solo acepta pagos en una moneda.

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

Código multi-moneda​

Usa este código si tu tienda Shopify admite pagos en varias divisas.

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 <currency> per 1 <SHOP_CURRENCY>, 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);
}
});

Después de haber añadido el código, haz clic en guardar. Comprueba también que tus permisos estén configurados correctamente, de lo contrario el script no se ejecutará si falta un permiso de usuario. Luego haz clic en "Connect". La integración ya está instalada correctamente.

El píxel creado «varify-checkout» con su código que se suscribe al evento «varify» y lee los datos de la variación

Paso 3: Añade objetivos de Shopify a tu experimento​

Asegúrate de que tu configuración de tracking esté establecida, de que hayas seleccionado GA4 como proveedor de tracking y de que el elemento "Test Evaluation" esté configurado en "In Varify and GA4". Si es así, aparecerá un "Results Link" para los experimentos «recién creados» en la parte superior derecha del experimento iniciado.

Al hacer clic en "Results Link" se abre el informe de resultados correspondiente del experimento. Ahora puedes añadir "Conversions" o "Revenue" como objetivo haciendo clic en "Add Shopify Goal". Los visitantes del objetivo se obtienen de GA4. El valor de conversión y el valor de ingresos provienen directamente de Shopify.

Importante: los datos de GA4 solo se actualizan después de aproximadamente 24 horas. Los datos de Shopify se actualizan en tiempo real.

Evaluación de objetivos de Varify con dos objetivos: «Conversions» mejora un 9,49 % y es significativo, «Revenue» un 9,73 % con un 94,28 % de confianza y no lo es

Filtro de productos en el informe de resultados​

Analiza el rendimiento de determinados productos en tu test A/B. Puedes ver productos individuales o variantes de producto. Para ello, introduce un ID de producto o varios IDs en el campo, separados por comas.