Varify Realtime Tracking
Contas criadas a partir de 17 de setembro de 2026 podem selecionar „Varify Realtime Tracking“ diretamente em „Tracking Setup.“ Clientes existentes podem solicitar esta funcionalidade através do nosso formulário de contacto. A integração em conjunto com GA4 está disponível para clientes existentes mediante pedido.
Em resumo
O Varify Realtime Tracking permite-te analisar as tuas experiências diretamente no Varify sem qualquer atraso. Um serviço de tracking dedicado do Varify mede os teus objetivos definidos e apresenta-os no Varify Reporting.
Podes usá-lo de duas formas:
- Como fornecedor de tracking: o Varify mede as tuas experiências por conta própria e serve de base para os teus relatórios. Seleciona-o em „Tracking Setup.“
- Como integração em conjunto com GA4: configuraste o Google Analytics 4 em „Tracking Setup“ e ativas adicionalmente o Varify Realtime Tracking em „Integrations.“
Fornecedor ou integração?
| Como fornecedor de tracking | Como integração em conjunto com GA4 | |
|---|---|---|
| Onde ativas | „Tracking Setup,“ cartão „Varify Realtime Tracking“ | „Integrations,“ cartão „Varify Realtime Tracking“ |
| Requisito | Disponível para a tua conta | GA4 está configurado em „Tracking Setup,“ e a integração está ativada para a tua conta |
| Consentimento do utilizador | Escolhes durante a configuração | Herdado da tua configuração GA4 |
| Os relatórios mostram | Dados do Varify Realtime Tracking | Objetivos do Varify e objetivos do GA4 (também BigQuery), Shopify opcional |
| Objetivos do GA4 | Não podem ser importados | Importados normalmente |
Usar o Varify Realtime Tracking como fornecedor de tracking
Passo 1: Configura o teu sistema de tracking
Vá para „Tracking Setup“ e selecione „Varify Realtime Tracking.“ Em seguida, podes decidir se queres integrar o Varify Realtime Tracking com ou sem „User Consent.“ Esta página pode ajudar-te a tomar essa decisão: https://varify.io/varify-tracking-gdpr-compliance/
Etapa 2: Ativando o Varify Realtime Tracking
Integração sem consentimento do usuário
Se decidiste carregar o Varify Realtime Tracking sem consentimento do utilizador, a tua configuração básica de tracking já está em vigor e podes continuar com „Configurar os teus objetivos“ abaixo.
Integração com consentimento do usuário
É fornecido um script de ativação para integração com o consentimento do usuário. O tracking é ativado assim que este script é carregado. Integre o script de acordo com as especificações da sua plataforma de gestão de consentimento (CMP). Você também pode usar o Google Tag Manager (GTM) para integração com base em visualizações de página e um sinal de consentimento válido.
Enviar dados para o GA4
Para também enviares os dados das tuas experiências para o GA4, ativa a integração Google Analytics 4 em „Integrations.“ Os objetivos do GA4 não podem ser importados nesta configuração. Podes encontrar a configuração aqui: Google Analytics 4 Direct Integration
Usar o Varify Realtime Tracking como integração
Usa esta opção se o GA4 for o teu fornecedor de tracking e quiseres que o Varify meça adicionalmente as tuas experiências. Os relatórios mostram então os objetivos do Varify e os objetivos do GA4 em conjunto, além dos dados do BigQuery, e dados do Shopify se o tiveres ativado.
Passo 1: Configurar o GA4 em Tracking Setup
Configura primeiro o Google Analytics 4. Podes encontrar as opções aqui: Set up tracking
Passo 2: Ativar a integração
- Abra „Integrations“ na navegação principal.
- Encontre o cartão „Varify Realtime Tracking“ e ative o toggle.
Não consegue ativar? A integração tem de ser ativada para a sua conta primeiro. Envie-nos uma mensagem através do nosso formulário de contacto. Entraremos em contacto consigo.
Passo 3: Consentimento do utilizador
O Varify Realtime Tracking segue a sua configuração do GA4. Se o GA4 só rastrear após o consentimento do utilizador, o Varify Realtime Tracking também só começa após o consentimento ter sido dado. Para mais detalhes, consulte Enviar dados para a sua ferramenta de análise web apenas após o consentimento do utilizador.
Configure as suas metas
Para analisar as suas experiências, crie metas. Vai encontrar a secção "Goals" na barra lateral à esquerda. As metas são criadas da mesma forma em ambas as configurações.
Na seção "Goals", podes escolher entre três tipos de metas: Click Goal, Page View Goal e Custom Goal.
Configurar Metas de Clique
Com Click Goals, você pode medir com que frequência os participantes do experimento clicam em elementos. Para isso, defina um seletor personalizado para o elemento. Para identificar um seletor personalizado para um elemento, você pode usar, por exemplo, o editor visual da Varify. Abra-o e selecione o elemento que deseja rastrear. Em seguida, copie o seletor e insira-o no campo "Selector" em "Click Goal".
Configurar Metas de Visualização de Página
Com Metas de Visualização de Página, você pode medir com que frequência os participantes do seu experimento visualizam páginas ou tipos de página específicos. Para isso, você pode simplesmente definir suas próprias regras personalizadas para rastrear as visualizações de página que atendem aos seus critérios.
Configurar Metas Personalizadas
Você pode usar Custom Goals para criar metas personalizadas. Por exemplo, isso pode ser quando você deseja medir o valor de receita de um pedido. As Custom Goals são acionadas via JavaScript no seu experimento.
Contagem de Conversões
Com essa configuração, você pode especificar se uma meta deve ser rastreada uma única vez ou várias vezes. Por padrão, em testes A/B, cada meta é rastreada apenas uma vez, já que normalmente você só está interessado em saber se um usuário atingiu a meta ou não. No entanto, o rastreamento múltiplo pode ser útil para metas de receita: nesse caso, o valor do pedido de um usuário pode ser acumulado em várias sessões.
Rastreamento de Metas de Checkout do Shopify
Metas de eventos do Shopify como «Purchase» ou «Start Checkout» não podem ser rastreadas diretamente através do seu site. A partir de 2025, o Shopify tornou obrigatório que essas só possam ser rastreadas através do custom pixel no seu site. Para rastrear essas metas, vá para «Settings > Customer Events» e clique no botão «Add Custom Pixel». Defina o nome do pixel como «Varify Realtime Tracking» e clique em «Add Pixel».
Depois de criar o pixel, primeiro abra a seção „Customer Privacy“ e defina "Data Sale" como "Data collected does not qualify as a data sale." A Varify não vende dados e rastreia apenas valores agregados para as análises do seu experimento.
Para «Permission», você deve selecionar «Not required» se quiser rastrear sem o consentimento do usuário — conforme especificado anteriormente na configuração de tracking. Se, por outro lado, você estiver rastreando com o consentimento do usuário, pode deixar a configuração como «Required».
Em seguida, copie o código destacado abaixo deste parágrafo e cole-o em «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));
});
Depois de adicionares o código, clica em "Connect" no menu no topo. A tua configuração de tracking do Shopify está agora concluída.
Adicione os objetivos do Shopify no Varify
O pixel personalizado apenas envia os eventos. Para usá-los nos seus experimentos, primeiro registe cada evento do Shopify como um Custom Event Goal no Varify. Só precisa de fazer isto uma vez por objetivo. Depois, pode selecionar os objetivos em qualquer experimento.
O nome do objetivo fica ao seu critério. O event name, no entanto, tem de ser exatamente igual ao nome do evento que o pixel envia, incluindo maiúsculas, minúsculas e underscores. Caso contrário, o Varify não consegue corresponder os eventos e o objetivo não vai registar nenhum dado.
| Evento do Shopify | Event name no Varify (exatamente como enviado pelo pixel) | Exemplo de nome de objetivo |
|---|---|---|
product_added_to_cart | add_to_cart | Add to Cart |
checkout_started | checkout_started | Begin checkout |
checkout_completed | checkout_completed | Purchase |
O pixel envia o evento do Shopify product_added_to_cart para o Varify como add_to_cart. Use add_to_cart como event name no Varify.
Passo 1: Criar um novo Custom Event Goal
- Vá a „Goals“ na barra lateral à esquerda e crie um novo objetivo.
- Selecione o tipo de objetivo „Custom event“ e clique em „Next.“
Passo 2: Inserir o goal name e o event name
- Insira o goal name que quiser, por exemplo „Purchase.“ Este nome é mostrado nos relatórios.
- Insira o event name exatamente como indicado na tabela acima, por exemplo
checkout_completed.
O event name é preenchido automaticamente a partir do goal name. Substitua-o pelo event name exato da tabela. Não pode ser alterado depois, por isso verifique antes de continuar.
Passo 3: Escolher a frequência de contagem e guardar
No passo 3, escolha a frequência com que o objetivo deve contar („Once per visitor“ ou „Every time“). Para um objetivo de conversão normal como „Purchase,“ mantenha „Once per visitor.“ Depois clique em „Create goal.“
O snippet de tracking mostrado neste passo não é necessário para os objetivos do Shopify, porque o pixel personalizado já envia o evento.
Medir receita (receita por visitante)
Para analisar a receita, por exemplo a receita por visitante, cria outro Custom Event Goal:
- Dá-lhe um nome, por exemplo, «Revenue.»
- Insere
checkout_completedcomo nome do evento, o mesmo que para «Purchase.» - No passo 3, ativa «Track revenue» («Measure order value alongside conversions»).
- Clica em «Create goal.»
Adiciona os goals à tua experiência
Depois de criados os Custom Event Goals, podes adicioná-los a uma experiência:
- Abre a tua experiência e vai ao separador «Goals».
- Clica em «+ Custom Event Goal» e seleciona o teu goal na lista, por exemplo «Purchase» ou «Revenue.»
- Clica em «Save Goals.»
O goal aparece agora na lista, marcado como «Custom event.»
Adiciona os teus goals antes de iniciares a experiência. Goals adicionados mais tarde não são monitorizados retroativamente.