Varify Realtime Tracking
Las cuentas creadas a partir del 17 de septiembre de 2026 pueden seleccionar «Varify Realtime Tracking» directamente en «Tracking Setup». Los clientes existentes pueden solicitar esta función a través de nuestro formulario de contacto. La integración junto con GA4 está disponible para clientes existentes bajo solicitud.
En resumen
Varify Realtime Tracking te permite analizar tus experimentos directamente en Varify sin demora. Un servicio de tracking dedicado de Varify mide tus objetivos definidos y los muestra en Varify Reporting.
Puedes usarlo de dos formas:
- Como tu proveedor de tracking: Varify mide tus experimentos por sí solo y es la base de tus informes. Lo seleccionas en «Tracking Setup».
- Como integración junto con GA4: Has configurado Google Analytics 4 en «Tracking Setup» y además activas Varify Realtime Tracking en «Integrations».
¿Proveedor o integración?
| Como proveedor de tracking | Como integración junto con GA4 | |
|---|---|---|
| Dónde lo activas | «Tracking Setup», tarjeta «Varify Realtime Tracking» | «Integrations», tarjeta «Varify Realtime Tracking» |
| Requisito | Disponible para tu cuenta | GA4 está configurado en «Tracking Setup», y la integración está habilitada para tu cuenta |
| Consentimiento del usuario | Lo eliges durante la configuración | Se toma de tu configuración de GA4 |
| Los informes muestran | Datos de Varify Realtime Tracking | Objetivos de Varify y objetivos de GA4 (también BigQuery), Shopify opcional |
| Objetivos de GA4 | No se pueden importar | Se importan como de costumbre |
Usar Varify Realtime Tracking como tu proveedor de tracking
Paso 1: Configura tu sistema de tracking
Ve a „Tracking Setup“ y selecciona „Varify Realtime Tracking“. Después puedes decidir si integrar Varify Realtime Tracking con o sin „User Consent“. Esta página puede ayudarte a tomar esa decisión: https://varify.io/varify-tracking-gdpr-compliance/
Paso 2: Activar Varify Realtime Tracking
Integración sin consentimiento del usuario
Si has decidido cargar Varify Realtime Tracking sin consentimiento del usuario, tu configuración básica de tracking ya está lista, y puedes continuar con «Set up your goals» más abajo.
Integración con consentimiento del usuario
Se proporciona un script de activación para la integración con el consentimiento del usuario. El tracking se activa en cuanto se carga este script. Integra el script según las especificaciones de tu plataforma de gestión de consentimiento (CMP). También puedes usar Google Tag Manager (GTM) para la integración basada en vistas de página y una señal de consentimiento válida.
Enviar datos a GA4
Para enviar también tus datos de experimentos a GA4, activa la integración de Google Analytics 4 en «Integrations». Los objetivos de GA4 no se pueden importar en esta configuración. Puedes encontrar la configuración aquí: Google Analytics 4 Direct Integration
Usar Varify Realtime Tracking como integración
Usa esto si GA4 es tu proveedor de tracking y quieres que Varify mida tus experimentos adicionalmente. Los informes entonces muestran los objetivos de Varify y los objetivos de GA4 juntos, además de datos de BigQuery, y datos de Shopify si lo has habilitado.
Paso 1: Configura GA4 en Tracking Setup
Configura primero Google Analytics 4. Puedes encontrar las opciones aquí: Set up tracking
Paso 2: Activa la integración
- Abre «Integrations» en la navegación principal.
- Busca la tarjeta «Varify Realtime Tracking» y activa el interruptor.
¿No puedes activarlo? La integración debe estar habilitada primero para tu cuenta. Envíanos un mensaje a través de nuestro formulario de contacto. Nos pondremos en contacto contigo.
Paso 3: Consentimiento del usuario
Varify Realtime Tracking sigue la configuración de tu GA4. Si GA4 solo rastrea después del consentimiento del usuario, Varify Realtime Tracking también se inicia solo después de que se haya dado el consentimiento. Para más detalles, consulta Enviar datos a tu herramienta de analítica web solo después del consentimiento del usuario.
Configura tus objetivos
Para analizar tus experimentos, crea objetivos. Encontrarás la sección "Goals" en la barra lateral izquierda. Los objetivos se crean de la misma forma en ambas configuraciones.
En la sección "Goals", puedes elegir entre tres tipos de objetivos: Click Goal, Page View Goal y Custom Goal.
Configuración de objetivos de clic
Con Click Goals, puedes medir con qué frecuencia los participantes del experimento hacen clic en elementos. Para ello, define un selector personalizado para el elemento. Para identificar un selector personalizado para un elemento, puedes usar el editor visual de Varify, por ejemplo. Ábrelo y selecciona el elemento que quieres trackear. Luego copia el selector e introdúcelo en el campo "Selector" bajo "Click Goal".
Configuración de objetivos de vista de página
Con Page View Goals, puedes medir con qué frecuencia los participantes de tu experimento visualizan páginas o tipos de página específicos. Para ello, simplemente puedes definir tus propias reglas personalizadas para rastrear las visualizaciones de página que cumplan tus criterios.
Configuración de objetivos personalizados
Puedes usar Custom Goals para crear objetivos personalizados. Por ejemplo, esto podría ser cuando quieres medir el valor de ingresos de un pedido. Custom Goals se activan mediante JavaScript en tu experimento.
Recuento de conversiones
Con este ajuste, puedes especificar si un objetivo debe rastrearse una vez o varias veces. Por defecto, en pruebas A/B, cada objetivo se rastrea solo una vez, ya que normalmente solo te interesa saber si un usuario alcanzó el objetivo o no. Sin embargo, el rastreo múltiple puede ser útil para objetivos de ingresos: en este caso, el valor del pedido de un usuario puede acumularse a lo largo de varias sesiones.
Seguimiento de objetivos de checkout de Shopify
Los objetivos de eventos de Shopify como «Purchase» o «Start Checkout» no se pueden trackear directamente a través de tu sitio web. A partir de 2025, Shopify ha hecho obligatorio que estos solo se puedan trackear a través del pixel personalizado en tu sitio web. Para trackear estos objetivos, ve a «Settings > Customer Events» y haz clic en el botón «Add Custom Pixel». Establece el nombre del pixel como «Varify Realtime Tracking» y haz clic en «Add Pixel».
Después de crear el pixel, primero abre la sección „Customer Privacy“ y establece "Data Sale" en "Data collected does not qualify as a data sale." Varify no vende datos y solo rastrea valores agregados para tus análisis de experimentos.
Para "Permission", debes seleccionar "Not required" si quieres trackear sin el consentimiento del usuario, tal como se especificó anteriormente en la configuración del tracking. Si, por el contrario, estás trackeando con el consentimiento del usuario, puedes dejar la configuración en "Required".
A continuación, copia el código resaltado debajo de este párrafo y pégalo en "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,
);
};
// 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));
});
Después de añadir el código, haz clic en «Connect» en el menú superior. Tu seguimiento de Shopify ya está configurado.
Añade los goals de Shopify en Varify
El custom pixel solo envía los eventos. Para usarlos en tus experimentos, primero registra cada evento de Shopify como un Custom Event Goal en Varify. Solo tienes que hacerlo una vez por goal. Después, puedes seleccionar los goals en cualquier experimento.
El nombre del goal lo eliges tú. Sin embargo, el event name debe ser exactamente igual al nombre del evento que envía el pixel, incluyendo mayúsculas, minúsculas y guiones bajos. De lo contrario, Varify no puede hacer coincidir los eventos y el goal no registrará ningún dato.
| Evento de Shopify | Nombre del evento en Varify (exactamente como lo envía el pixel) | Nombre de goal de ejemplo |
|---|---|---|
product_added_to_cart | add_to_cart | Add to Cart |
checkout_started | checkout_started | Begin checkout |
checkout_completed | checkout_completed | Purchase |
El pixel envía el evento de Shopify product_added_to_cart a Varify como add_to_cart. Usa add_to_cart como event name en Varify.
Paso 1: Crea un nuevo Custom Event Goal
- Ve a «Goals» en la barra lateral de la izquierda y crea un nuevo goal.
- Selecciona el tipo de goal «Custom event» y haz clic en «Next.»
Paso 2: Introduce el goal name y el event name
- Introduce el goal name que prefieras, por ejemplo «Purchase.» Este nombre se muestra en los informes.
- Introduce el event name exactamente como se muestra en la tabla anterior, por ejemplo
checkout_completed.
El event name se rellena automáticamente a partir del goal name. Sobrescríbelo con el event name exacto de la tabla. No se puede cambiar más tarde, así que revísalo antes de continuar.
Paso 3: Elige con qué frecuencia cuenta y guarda
En el paso 3, elige con qué frecuencia debe contar el goal («Once per visitor» o «Every time»). Para un goal de conversión normal como «Purchase,» mantén «Once per visitor.» Luego haz clic en «Create goal.»
El fragmento de tracking que se muestra en este paso no es necesario para los goals de Shopify, porque el custom pixel ya envía el evento.
Medir ingresos (ingresos por visitante)
Para analizar ingresos, por ejemplo ingresos por visitante, crea otro Custom Event Goal:
- Asígnale un nombre, por ejemplo, «Revenue».
- Introduce
checkout_completedcomo nombre de evento, el mismo que para «Purchase». - En el paso 3, activa «Track revenue» («Measure order value alongside conversions»).
- Haz clic en «Create goal».
Añade los objetivos a tu experimento
Una vez creados los Custom Event Goals, puedes añadirlos a un experimento:
- Abre tu experimento y ve a la pestaña «Goals».
- Haz clic en «+ Custom Event Goal» y selecciona tu objetivo de la lista, por ejemplo «Purchase» o «Revenue».
- Haz clic en «Save Goals».
El objetivo ahora aparece en la lista, marcado como «Custom event».
Añade tus objetivos antes de iniciar el experimento. Los objetivos añadidos más tarde no se registran retroactivamente.