Shopify customer event pixel
Tracke Revenue, Checkout-Schritte und Goals auf Produktebene in Shopify mit einem Custom Pixel.
Mit einem Custom Pixel (Shopify Customer events) erfasst ABlyft die clientseitigen Events von Shopify über die Web Pixel API. So kannst du auch die Schritte des Checkouts verfolgen, nicht nur den Kauf. Hintergründe findest du in der Shopify-Dokumentation zu Customer Events und Pixeln.
Customer Events können blockiert werden und sind weniger zuverlässig als der Webhook. Siehe den Vergleich, bevor du beide kombinierst.
Einrichtung
- Öffne in Shopify Settings → Customer events.
- Wähle Add custom pixel und gib ihm einen Namen, zum Beispiel
ABlyft Tracking. - Füge den Code unten ein und ändere die
projectIdauf die ID deines ABlyft-Projects. Das ist erforderlich. - Passe den
eventNamejedes Goals im Code an, falls er von den API names in ABlyft abweicht. - Füge den Pixel hinzu, speichere ihn und verbinde ihn.
Um Käufe und ähnliche Events zu tracken, prüfe die Customer privacy-Einstellungen des Pixels in Shopify. Events werden nur gesendet, wenn die Einstellung es für den Visitor erlaubt.
Code zum Kopieren
Einstellungen am Anfang des Codes:
| Konstante | Bedeutung |
|---|---|
projectId | Die ID deines ABlyft-Projects. Ersetze den Platzhalter. |
trackingCurrency | Die Währung, in der Revenue getrackt wird. Standard "EUR". Bestellungen in einer anderen Währung werden umgerechnet. |
maxOrderValueFallback | Standardmäßig false. Gibst du eine Zahl ein, zum Beispiel 250.50, wird ein höheres Revenue auf diesen Wert gekappt. |
/*
IMPORTANT: Edit the Project ID
*/
const projectId = 123456789; // ← Your project id
//Further options (do not change if not necessary)
const trackingCurrency = "EUR"; // ← currency you want to track in
const maxOrderValueFallback = false; // ← if a number (i.e., 250.50) is set, a revenue is capped to that
analytics.subscribe("all_events", async ({ name, data, context }) => {
if (!(name.startsWith("checkout_") || name === "product_added_to_cart")) {
return;
}
console.log("ABlyft - Shopify pixel event received:", name);
const snippetVersion = 2.30;
const d = document;
const w = window;
w.insideShopifyPixel = true;
w.ablyftAllowIframe = true;
w.shopifyEventContext = context;
let j = d.createElement("script");
j.async = true;
j.src = `https://cdn.ablyft.com/s/${projectId}.js`;
j.onload = async () => {
const url = context.window.location.href;
const { checkout } = data;
let events = [{ eventType:"custom", eventName:`shopify_event_${name}`}];
//currency conversion
try {
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("ABlyft - Timeout loading currency script"));
}, 1000); // 1 sec timeout
const fxScript = document.createElement("script");
fxScript.src = "https://cdn.shopify.com/s/javascripts/currencies.js";
fxScript.onload = () => {
clearTimeout(timeout);
if (typeof Currency?.convert === "function") {
resolve();
} else {
reject(new Error("ABlyft - Currency.convert impossible"));
}
};
fxScript.onerror = () => {
clearTimeout(timeout);
reject(new Error("ABlyft - Currency script could not be loaded"));
};
document.head.appendChild(fxScript);
});
} catch (error) {
console.warn("ABlyft - Currency conversion not available:", error);
}
//event handling
if(name === "checkout_completed"){
const { amount: rawAmount, currencyCode: sourceCurrency } = checkout.totalPrice;
const count = checkout.lineItems.reduce((sum, {quantity}) => sum + quantity, 0);
let revenue = rawAmount;
let conversionFactor = 1;
if(sourceCurrency !== trackingCurrency) {
try {
if (typeof Currency?.convert === "function") {
revenue = Currency.convert(rawAmount, sourceCurrency, trackingCurrency);
conversionFactor = revenue / rawAmount;
console.log(`ABlyft - Currency conversion successful: from ${sourceCurrency} ${rawAmount} to ${trackingCurrency} ${parseFloat(revenue.toFixed(2))}`);
} else {
throw new Error("ABlyft - Currency.convert not available");
}
} catch (e) {
console.warn("ABlyft - Currency conversion failed – using rawAmount", e);
revenue = rawAmount;
}
}
if(typeof maxOrderValueFallback === "number"){
if(revenue > maxOrderValueFallback) revenue = maxOrderValueFallback;
}
//use your own eventNames here
events = events.concat([
{ eventType:"revenue", eventName:"revenue", eventValue: parseFloat(revenue.toFixed(2)) },
{ eventType:"custom", eventName:"products-count", eventValue: count },
{ eventType:"custom", eventName:"purchase" }
]);
//custom product-related goals
let lineItemsMap = {};
checkout.lineItems.forEach(function(item) {
const productId = item.variant?.product?.id || item.id;
const amount =
item.finalLinePrice?.amount ??
item.totalPrice?.amount ??
item.linePrice?.amount ??
item.discountedTotalPrice?.amount ??
item.originalTotalPrice?.amount ??
0;
const revenue = Number(amount) || 0;
if (!lineItemsMap[productId]) {
lineItemsMap[productId] = { productId, revenue: 0 };
}
lineItemsMap[productId].revenue += revenue;
});
let lineItemsArray = Object.values(lineItemsMap);
lineItemsArray.forEach(function(item) {
events.push({ eventType: "revenue", eventName: "revenue-product-" + item.productId, eventValue: parseFloat((item.revenue*conversionFactor).toFixed(2)) });
events.push({ eventType: "custom", eventName: "purchase-product-" + item.productId });
});
}
console.log("ABlyft - Events to send:", events);
let storageType = w.ablyft.get("data").storage;
if(storageType === "default") storageType = "cookie";
async function getStorageItem(key) {
if(storageType === "cookie"){
return await browser.cookie.get(key);
}
return await browser[storageType].getItem(key);
}
async function setStorageItem(key, value) {
if(storageType === "cookie"){
return await browser.cookie.set(key, value);
}
return await browser[storageType].setItem(key, value);
}
let userData = await getStorageItem("ablyft_uvs");
const user = JSON.parse(userData);
let visitor_type = "new";
if (user && user.sessions && user.sessions > 1) {
visitor_type = "returning";
}
const attributes = {
device_type: w.ablyft.get("deviceType"),
visitor_type,
url,
user_agent: w.navigator.userAgent
};
let experimentsBucketData = await getStorageItem("ablyft_exps");
if(!experimentsBucketData){
console.log("ABlyft - Empty user bucketing - aborting");
return;
}
const experimentsBucket = JSON.parse(experimentsBucketData);
const experimentIds = Object.keys(experimentsBucket);
let variationIds = [];
for (let i = 0; i < experimentIds.length; i++) { variationIds.push(experimentsBucket[experimentIds[i]]); }
function createLockId() {
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let text = "";
for (let i = 0; i < 10; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); }
return text;
}
const goals = w.ablyft.get("goals");
const tgoalsData = await getStorageItem("ablyft_tgoals");
const triggeredGoals = tgoalsData ? JSON.parse(tgoalsData) : {};
const postObj = {
projectId: projectId,
source: "shopify-webpixel",
snippetVersion,
user: {
attributes,
experimentsBucket
},
events: events.filter(event => {
const goalObject = goals.find(goal => goal.api_name === event.eventName);
if(!goalObject) return false;
return !(goalObject.counting_method === "one" && triggeredGoals[goalObject.id]);
}).map(event => {
const goalObject = goals.find(goal => goal.api_name === event.eventName);
return {
type: event.eventType,
value: event.eventValue,
timestamp: Math.floor(Date.now() / 1000),
locked: Math.floor(Date.now() / 1000),
lockId: createLockId(),
goal: {
id: goalObject.id,
variations: variationIds
}
};
})
}
if(postObj.events.length === 0) return;
const sent = await sendWithRetry(postObj);
if(sent){
postObj.events.forEach(event => {
const goalId = event.goal.id;
const existing = triggeredGoals[goalId] || [];
triggeredGoals[goalId] = Array.from(new Set([...existing, ...variationIds]));
});
await setStorageItem("ablyft_tgoals", JSON.stringify(triggeredGoals));
}
async function sendWithRetry(data, maxRetries = 3) {
const payload = JSON.stringify(data);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch("https://log.ablyft.com", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: payload
});
if (response.ok) {
console.log(`ABlyft - Request successful on attempt ${attempt}`);
return true;
} else if (response.status >= 400 && response.status < 500) {
console.warn(`ABlyft - Client error (${response.status}) – no retry`);
return false;
} else {
console.warn(`ABlyft - Server error (${response.status}) – retrying...`);
}
} catch (error) {
console.warn(`ABlyft - Network error on attempt ${attempt}:`, error);
}
if (attempt < maxRetries) {
await new Promise((res) => setTimeout(res, 500)); //delay in ms
}
}
console.error("ABlyft - All retry attempts failed.");
return false;
}
};
d.head.appendChild(j);
});Was der Pixel sendet
- Er reagiert auf die Events
product_added_to_cartund allecheckout_*-Events, lädt dein ABlyft-Snippet und sendet die Events an ABlyft. Er sendet nur Events für Goals, die in deinem Project existieren (zugeordnet über den API name). - Bei
checkout_completedsendet er die Goalsrevenue,products-countundpurchasesowie die unten beschriebenen Goals auf Produktebene. - Der Visitor muss bereits Experiments zugewiesen sein (der Pixel liest die Zuweisung aus dem ABlyft-Storage). Gibt es keine, wird nichts gesendet.
- Eine fehlgeschlagene Anfrage wird bis zu dreimal wiederholt (nicht bei Client-Fehlern).
Weitere Events
Erstelle ein Custom Goal und verwende einen dieser Namen als API name:
| API name | Wird ausgelöst, wenn |
|---|---|
shopify_event_product_added_to_cart | ein Produkt in den Warenkorb gelegt wird |
shopify_event_checkout_started | der Checkout gestartet wird |
shopify_event_checkout_contact_info_submitted | Kontaktdaten abgesendet werden |
shopify_event_checkout_address_info_submitted | die Adresse abgesendet wird |
shopify_event_checkout_shipping_info_submitted | Versanddaten abgesendet werden |
shopify_event_checkout_completed | der Checkout abgeschlossen wird (alternativ purchase oder revenue verwenden) |
Events auf Produktebene
Für die Messung pro Produkt sendet der Pixel für jedes Produkt einer abgeschlossenen Bestellung zwei zusätzliche Events:
| API name | Bedeutung |
|---|---|
revenue-product-[productId] | Revenue dieses Produkts |
purchase-product-[productId] | Käufe, die dieses Produkt enthalten |
Erstelle ein Goal und ersetze den Platzhalter durch die Shopify product ID, zum Beispiel revenue-product-1234567890.
Die Werte werden pro Produkt summiert, nicht pro Variante: Die Revenues aller Varianten werden unter einer Produkt-ID gesammelt.