SDK-Dokumentation

Pixel SDK

Pixel-Installation

Installiere den Adverfly-Tracking-Pixel auf deiner Website

Der Adverfly-Pixel ist ein JavaScript-Snippet, das Nutzer-Interaktionen auf deiner Website trackt. Er erfasst Pageviews, Events und Conversions und füttert deine Analytics.

Schnellstart

Füge den folgenden Code in den <head>-Bereich deiner Website ein:

Request
<script>
  window.adverfly = window.adverfly || [];
  function advPxl() {
    var args = [false];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    adverfly.push(args);
  }
  advPxl("init", DEINE_WORKSPACE_ID);
  window.adverfly.store_currency = "EUR";
  window.adverfly.store_timezone = "Europe/Berlin";

  var script = document.createElement("script");
  script.type = "text/javascript";
  script.async = true;
  script.src = "https://sos-de-fra-1.exo.io/adv/advv4.01.js";
  document.getElementsByTagName("head")[0].appendChild(script);
</script>

Ersetze DEINE_WORKSPACE_ID durch deine Workspace-ID aus dem Adverfly-Dashboard.

Konfiguration

ParameterTypeRequiredDescription
workspace_idnumberRequiredDeine Adverfly-Workspace-ID
store_currencystringRequiredDie Basiswährung deines Shops (z. B. EUR, USD)
store_timezonestringRequiredDie Zeitzone deines Shops (z. B. Europe/Berlin)

Währungsumrechnung

Die store_currency-Einstellung ist wichtig für genaue Umsatz-Tracking. Wenn eine Transaktion in einer anderen Währung eingeht (z. B. zahlt ein Kunde in USD), rechnet Adverfly diese automatisch zum aktuellen Wechselkurs in deine Shop-Währung um.

Beispiel: Deine Shop-Währung ist EUR. Ein Kunde zahlt 129 USD. Adverfly rechnet das in deinen Reports auf ~119 € um.

Zeitzone

Die store_timezone stellt sicher, dass alle Events und Conversions in deiner lokalen Zeit erfasst werden — das macht deine Reports einfacher zu lesen und auszuwerten.

Events tracken

Custom-Events mit dem Adverfly-Pixel tracken

Sobald der Pixel installiert ist, kannst du Custom-Events tracken, um Nutzer-Interaktionen zu erfassen.

Grundlegendes Event-Tracking

Verwende die advPxl-Funktion, um Events zu tracken:

Request
// Add to Cart
advPxl("event", "add_to_cart");

// Initiated Checkout
advPxl("event", "initiated_checkout");

Standard-Events

CodeDescription
pageviewNutzer sieht eine Seite (wird automatisch beim Init getrackt)
add_to_cartNutzer legt ein Produkt in den Warenkorb
initiated_checkoutNutzer startet den Checkout

Autocapture

Das v3-Pixel kann Nutzer-Interaktionen automatisch erfassen — Klicks, Form-Submits, Input-Änderungen, Rage-Clicks, Dead-Clicks und Copy/Cut-Aktionen — ohne manuelle advPxl-Aufrufe. Standardmäßig deaktiviert: aktiviere es pro Workspace mit window.adverfly.activate_autocapture = true vor dem init.

Request
window.adverfly = window.adverfly || [];
window.adverfly.activate_autocapture = true;
advPxl("init", 8397799);

Erfasste Events

CodeDescription
$clickWird bei Klicks ausgelöst. Das Ziel wird auf das nächstgelegene interaktive Eltern-Element aufgelöst (a, button, input, select, textarea, label, form oder role=button|link|menuitem).
$submitWird bei Form-Submits ausgelöst. Enthält Action und Methode des Formulars.
$changeWird bei <select>-Änderungen sowie Checkbox-/Radio-Toggles ausgelöst. Werte von Text-Inputs werden NIEMALS erfasst.
$rageclickWird ausgelöst, wenn dasselbe Element innerhalb 1 Sekunde 3+ mal geklickt wird.
$dead_clickWird ausgelöst, wenn ein Klick auf einen Link oder Button innerhalb von 2,5 Sekunden überhaupt nichts verändert — keine DOM-Änderung, keine Navigation, kein Scrollen, nichts markiert. Das klassische Signal für einen kaputten Button.
$copyWird ausgelöst, wenn ein Nutzer Inhalt kopiert. Es werden ausschließlich Metadaten erfasst — niemals der kopierte Text selbst.
$cutWird ausgelöst, wenn ein Nutzer Inhalt ausschneidet. Nur Metadaten werden erfasst.

Privacy

Autocapture ist so designt, dass keine sensiblen Daten geleakt werden. Folgendes wird clientseitig erzwungen, bevor etwas gesendet wird:

  • Niemals erfasste Input-Typen: password, hidden, file.
  • Über Name/Autocomplete blockierte Felder: Kreditkarte (cc-*, card-num, card-no), CVC/CVV, Ablaufdatum, SSN, Sozialversicherung, Passwort, API-Keys, Auth-Tokens, Einmal-Codes.
  • Werte von Freitext-Inputs werden nie gelesen: <input type="text|email|tel|..."> und <textarea> werden nie erfasst. Nur <select>-Optionen sowie Checkbox-/Radio-Status.
  • Text-Scrubbing: Jeder Token in sichtbarem Text, der wie eine Kreditkartennummer, SSN oder eine Folge von 13+ Ziffern aussieht, wird vor dem Senden entfernt. Das Ergebnis wird auf 200 Zeichen gekürzt.
  • Opt-out-Selektoren: Elemente (und Kinder) mit Klasse adv-no-capture oder Attribut data-adv-no-capture werden komplett übersprungen.

Elemente ausschließen

Ein bestimmtes Element (und alle Kinder) ausschließen:

Request
<!-- via Klasse -->
<div class="adv-no-capture">
  <input type="text" name="internal-note" />
</div>

<!-- via Attribut -->
<section data-adv-no-capture>
  <button>Interne Aktion</button>
</section>

Konfiguration

Feinsteuerung via window.adverfly.autocapture_config (vor init setzen):

CodeDescription
url_allowlistArray aus Strings (Substring-Match) oder RegExp. Wenn gesetzt, läuft Autocapture nur auf URLs, die mindestens einem Eintrag entsprechen.
url_ignorelistArray aus Strings oder RegExp. Autocapture wird auf passenden URLs übersprungen.
element_allowlistArray kleingeschriebener Tag-Namen. Nur Elemente mit diesen Tags werden erfasst.
css_selector_allowlistArray von CSS-Selektoren. Nur passende Elemente werden erfasst.
Request
window.adverfly = window.adverfly || [];
window.adverfly.autocapture_config = {
  url_ignorelist: [/\/admin/, "/debug"],
  css_selector_allowlist: [".track-me", "[data-adv-track]"],
};
advPxl("init", 8397799);

Rate-Limiting

Eingebaute Limits schützen deine Seite und dein Event-Kontingent:

  • Global: max. 30 Autocapture-Events pro Sekunde.
  • Pro Element: mindestens 500 ms zwischen Events am selben Element.
  • Rage-Click: 1 s Cooldown pro Element nach einem $rageclick.
  • Dead-Click: max. 20 Meldungen und 60 beobachtete Klicks pro Seitenaufruf, höchstens vier gleichzeitig beobachtet.

Was nie als Dead-Click gewertet wird

Ein Klick gilt nur dann als tot, wenn ein funktionierendes Bedienelement sichtbar etwas getan hätte. Die folgenden erledigen ihre Aufgabe, ohne die Seite anzufassen — sie zu bewerten würde für einen Shop ohne kaputte Buttons welche erfinden:

  • Formularfelder und alles innerhalb eines <form>, dazu editierbare Inhalte
  • ein Link mit download, mit target="_blank" oder auf mailto:, tel: oder sms:
  • ein <canvas>, in dem ein Konfigurator Pixel malt statt Knoten
  • ein Custom Element oder alles, was in einen Shadow Root rendert und von außen nicht beobachtet werden kann
  • alles, was kein Link, Button, Summary oder Element mit interaktiver Rolle ist — gewöhnlicher Fließtext wird also nie gemeldet

Alles Weitere kannst du mit data-adv-no-dead-click am Element selbst oder an einem beliebigen Vorfahren ausnehmen.

Eine ehrliche Einschränkung: Lebendigkeit heißt „auf der Seite hat sich etwas verändert“. Eine Seite mit Countdown oder automatisch rotierendem Karussell verändert sich ständig, dort werden Dead-Clicks deshalb gar nicht erkannt. Die Prüfung irrt lieber ins Schweigen, als Meldungen zu erfinden.

Pageviews in einer Single-Page-App

Ein Storefront auf React, Vue oder Svelte meldet einen Pageview pro Hard-Navigation und nichts für die zwanzig Screens, die der Besucher tatsächlich durchlaufen hat. Ein Flag behebt das:

Request
window.adverfly = window.adverfly || [];
window.adverfly.activate_spa_pageviews = true;
advPxl("init", 8397799);

Das Pixel hängt sich dann in die History API und feuert einen pageview mit navigation: "spa", sobald sich der Pfad ändert.

Nur der Pfad, dazu der Hash, wenn er eine Route ist (#/products/socks) — Hash-geroutete Apps werden also mitgezählt. Ein einfacher Anker wie #reviews ist kein Screen. Filter- und Sortier-Steuerungen verändern meist nur den Query-String, und genau darüber läuft ein automatischer Pageview-Hook klassischerweise aus dem Ruder: Zwei Shops auf dieser Plattform haben an einem Tag bis zu 46.386 Pageviews von einem einzigen Besucher gesendet, weil irgendetwas den History-State in einer Schleife gepusht hat. Wenn dein Routing tatsächlich im Query-String liegt, schalte es bewusst dazu:

Request
window.adverfly.spa_track_query = true;

Drei weitere Schutzmechanismen greifen, keiner davon ist konfigurierbar:

  • Derselbe Pfad wird nie zweimal hintereinander gemeldet.
  • Änderungen werden um 300 ms entprellt: Ein Router, der den State während eines Übergangs dreimal ersetzt, erzeugt einen einzigen Pageview.
  • Nach 100 Pageviews in einem Seitenaufruf schaltet sich der Hook selbst ab und sagt das einmal in der Konsole, statt still weiterzufluten.

Auf einem Shopify-Storefront gehört der Event-Strom bereits dem Web Pixel, deshalb bleibt dieser Hook dort aus, auch wenn das Flag gesetzt ist. Beides zu zählen würde jeden Screen doppelt zählen.

Feature-Referenz

Jedes Pixel-Feature, das Flag zum Einschalten und was es kostet

Das Pixel wird als ein einziges Bundle ausgeliefert, aber fast nichts darin läuft, bevor du es einschaltest. Eine Standard-Installation trackt Pageviews, Conversions und Attribution — sonst nichts. Alles Weitere ist opt-in, pro Website, mit einer Zeile in deinem Snippet.

<script>
  window.adverfly = window.adverfly || [];
  advPxl("init", YOUR_WORKSPACE_ID);

  window.adverfly.activate_autocapture = true;
  window.adverfly.activate_web_vitals = true;
</script>

Die Flags müssen gesetzt sein, bevor das Bundle fertig geladen ist. Deshalb gehören sie in denselben Inline-Block wie advPxl("init", …) und nicht in ein Skript, das später läuft.

Features

FlagSchaltet einWas es kostet
activate_autocaptureKlicks, Formular-Absendungen, Rage-Clicks und Dead-Clicks, ohne irgendetwas zu taggenEin Event pro Interaktion
activate_ab_testingA/B-Tests, Rollouts und den visuellen EditorEine gecachte Config-Anfrage pro Besucher
activate_heatmapKlick- und Scroll-HeatmapsGebündelt, eine Anfrage pro Pageview
activate_session_replaySession-AufzeichnungenDas teuerste von allen. Unbedingt sampeln, siehe Heat Maps & Session Replays
activate_web_vitalsCore Web VitalsEin Event pro Pageview
activate_error_trackingNicht abgefangene JavaScript-Fehler und abgelehnte Promises. Einen Fehler selbst zu melden, mit advPxl("exception", err), braucht kein FlagEin Event pro eindeutigem Fehler, gedeckelt bei 10 pro Pageview
activate_spa_pageviewsEinen Pageview bei jedem clientseitigen RoutenwechselEin Event pro Routenwechsel
activate_widgetsPopups, Banner, Toasts und Umfragen, die das Pixel rendertEine gecachte Config-Anfrage pro Besucher
activate_loyaltyDen eingeloggten Loyalty-Bereich und das Guthaben-ModalAnfragen erst, sobald ein Kunde angemeldet ist
activate_upsellPost-Purchase- und In-Cart-AngeboteEine Config-Anfrage auf den Seiten, die es nutzen

Identität und Kontext

PropertyWas es tut
store_currencyISO-4217-Code für die Beträge, die du sendest, zum Beispiel "EUR"
store_timezoneIANA-Zone, zum Beispiel "Europe/Berlin". Bestimmt, auf welchen Tag ein Event fällt
customer_idDeine eigene Kundenkennung. Sie wird so übertragen, wie du sie setzt, und erst serverseitig vor dem Speichern gehasht — sende also eine Kennung, die du auf der Leitung akzeptabel findest
transaction_idDie aktuelle Bestellung, wird an Survey- und Upsell-Events angehängt
consent_level"denied", "functional" oder "marketing". Nur setzen, wenn du das Consent selbst auflöst

Das Pixel erkennt Cookiebot, Usercentrics, OneTrust und Consentmanager von allein. Ein manuell gesetztes consent_level überschreibt diese Erkennung — setze es also nur, wenn die Entscheidung bei dir liegt.

Feinjustierung

PropertyDefaultWas es tut
activate_offline_queuefalseParkt Events, die gescheitert sind, während der Browser offline war, und sendet sie später erneut. Aus, weil ein Replay auf dem Tag landet, an dem es ankommt, siehe Datenqualität
spa_track_queryfalseZählt einen SPA-Pageview, wenn sich nur der Query-String ändert. Aus, weil Filter- und Sortier-Parameter die klassische Quelle für Pageview-Schleifen sind
extra_bot_patternskeineZusätzliche User-Agent-Muster, die als Bot behandelt werden, als Strings oder reguläre Ausdrücke
disable_bot_detectionfalseAutomatisierten Traffic mitzählen, statt ihn zu verwerfen. Siehe Datenqualität
replay_configkeineSampling und Maskierung für Session Replay
is_devfalseRichtet das Pixel auf den Development-Stack

Ein Feature wieder abschalten

Die Zeile entfernen. Serverseitig wird nichts gespeichert: Das Flag wird bei jedem Seitenaufruf neu gelesen, ein Deploy ohne die Zeile stoppt die Erfassung also sofort. Bereits erfasste Daten bleiben erhalten.

Prüfen

In der Browser-Konsole auf einer beliebigen Seite, die das Pixel lädt:

window.adverfly                  // object, not undefined
window.adverfly.activate_heatmap // true when the flag reached the pixel
document.querySelector('script[src*="advv4"]')  // the bundle's script tag

Wenn ein Flag undefined liefert, obwohl es im Snippet steht, wurde es gesetzt, nachdem das Bundle es bereits gelesen hatte. Verschiebe es nach oben, in denselben Block wie advPxl("init", …).

Google Tag Manager

Adverfly via Google Tag Manager installieren

Du kannst den Adverfly-Pixel über Google Tag Manager installieren — leichter zu pflegen.

Installations-Schritte

  1. Öffne deinen GTM-Container
  2. Erstelle ein neues Tag
  3. Wähle Custom HTML
  4. Füge folgenden Code ein:
Request
<script>
  window.adverfly = window.adverfly || [];
  function advPxl() {
    var args = [false];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    adverfly.push(args);
  }
  advPxl("init", DEINE_WORKSPACE_ID);
  window.adverfly.store_currency = "EUR";
  window.adverfly.store_timezone = "Europe/Berlin";

  var script = document.createElement("script");
  script.type = "text/javascript";
  script.async = true;
  script.src = "https://sos-de-fra-1.exo.io/adv/advv4.01.js";
  document.getElementsByTagName("head")[0].appendChild(script);
</script>
  1. Setze den Trigger auf All Pages
  2. Speichern und veröffentlichen

Konfiguration

CodeDescription
store_currencyBasiswährung deines Shops. Transaktionen in anderen Währungen werden automatisch umgerechnet.
store_timezoneZeitzone deines Shops für genaue Event-Zeitstempel in Reports.

Conversions via Data Layer tracken

Wenn du den Data Layer für E-Commerce-Events nutzt:

Request
<script>
  // Dieses Tag sollte auf der Bestätigungs-Seite feuern
  advPxl("conversion", "purchase", {
    transaction_id: {{DL - Transaction ID}},
    transaction_gross_revenue: {{DL - Revenue}} * 100,
    transaction_currency: {{DL - Currency}}
  });
</script>

Custom-Events tracken

Lege weitere Tags für eigene Events an:

Request
<script>
  // Add-to-Cart-Tag — Trigger auf add_to_cart-Event
  advPxl("event", "add_to_cart");
</script>
Request
<script>
  // Initiated-Checkout-Tag — Trigger auf Checkout-Start
  advPxl("event", "initiated_checkout");
</script>

Empfohlene Trigger

CodeDescription
Base PixelAll Pages
Purchase Conversionpurchase-Event / Thank-you-Page
Add to Cartadd_to_cart-Event
Initiated Checkoutinitiated_checkout-Event

Shopify-Integration

Adverfly in deinem Shopify-Store installieren

Adverfly integriert sich nativ über Shopifys Customer-Events-API für genaues Tracking. Wenn du zusätzlich Personalisierungs-Widgets (Popups, Banner, Countdowns, Toasts) auf deinem Storefront ausspielen willst, ist ein zweiter winziger Schritt nötig — siehe „Optional: Widgets aktivieren" unten auf der Seite.

Installations-Schritte

  1. Gehe zum Shopify-Admin
  2. Navigiere zu Settings → Customer events
  3. Klicke auf Add custom pixel
  4. Nenne ihn „Adverfly“
  5. Füge folgenden Code ein:
Request
const script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = "https://sos-de-fra-1.exo.io/adv/script-shopify.js";
document.getElementsByTagName("script")[0].parentNode.appendChild(script);

window.adverfly_web_pixel = true;
window.adverfly_init = init;
window.adverfly_browser = browser;
window.adverfly_settings = api.settings;

window.adverfly = window.adverfly || [];
window.advPxl = function () {
  adverfly.push([false, ...arguments]);
};

analytics.subscribe("all_events", (event) => {
  window.adverfly.push(["all_shopify_events", event]);
  advPxl("init", DEINE_WORKSPACE_ID);
  window.adverfly.store_currency = "EUR";
  window.adverfly.store_timezone = "Europe/Berlin";

  if (event.name === "page_viewed") {
    advPxl("check", "vikeys", event);
  }
});

Ersetze DEINE_WORKSPACE_ID mit deiner Workspace-ID und setze Währung sowie Zeitzone deines Shops.

Konfiguration

CodeDescription
store_currencyBasiswährung deines Shops. Transaktionen in anderen Währungen werden automatisch umgerechnet.
store_timezoneZeitzone deines Shops für genaue Event-Zeitstempel in Reports.

Getrackte Events

Die Shopify-Integration trackt automatisch alle Standard-E-Commerce-Events:

CodeDescription
page_viewed → pageviewShopify-page_viewed-Event, in Adverfly als pageview erfasst
product_added_to_cart → add_to_cartShopify-Add-to-Cart-Event, in Adverfly als add_to_cart erfasst
checkout_started → initiated_checkoutShopify-Checkout-Start, in Adverfly als initiated_checkout erfasst
checkout_completed → purchaseShopify-Purchase-Event, in Adverfly als purchase-Conversion erfasst

Optional: Widgets aktivieren

Der Shopify-Web-Pixel oben läuft in einer Sandbox und kann zwar Events tracken, aber kein UI im Storefront rendern. Um Adverfly-Personalisierungs-Widgets (Popups, Banner, Countdowns, Toasts) zu aktivieren, ergänzt du ein zweites kleines Snippet in deinem Theme — gleiches Muster wie Google Analytics oder jedes andere Tag.

  1. Gehe in deinem Shopify-Admin auf Online Store → Themes → ⋯ → Edit code
  2. Öffne layout/theme.liquid
  3. Füge das Folgende direkt vor </head> ein:
Request
<script async src="https://cdn.adverfly.com/preset-pixel-adv.js"></script>
<script>
  window.adverfly = window.adverfly || [];
  window.adverfly.activate_widgets = true;
  advPxl("init", YOUR_WORKSPACE_ID);
</script>

Der Theme-Pixel erkennt automatisch dass er auf Shopify läuft und feuert keine Pageview-/Vikey-Events — dafür ist der Web-Pixel oben zuständig. Der Theme-Pixel existiert nur, um Widgets im Storefront-DOM zu rendern. Keine doppelten Events.

Wer nur Tracking will (keine Widgets), kann diesen Abschnitt überspringen. Der Web-Pixel deckt Tracking allein ab.

npm-Paket

Das Pixel per npm installieren — mit vollständigen TypeScript-Typen

@adverfly/pixel ist ein typisierter Wrapper für Anwendungen, die ihr JavaScript selbst bauen: Next.js, Nuxt, Remix, SvelteKit, ein Headless-Storefront. Wer Shopify, WooCommerce oder ein handgeschriebenes Theme nutzt, braucht das Paket nicht — das Snippet aus der Pixel-Installation leistet dasselbe.

Das Paket enthält keine Kopie des Pixels. Es lädt das Bundle vom CDN, genau wie das Snippet, damit ein Fix deine Website ohne Dependency-Update erreicht. Dazu kommen die typisierte Oberfläche, sicheres Server-Side-Rendering und eine Queue, die Aufrufe annimmt, bevor das Bundle geladen ist.

Keine Abhängigkeiten. Rund 1 KB in deinem Bundle.

Installation

npm install @adverfly/pixel

Verwendung

Request
import { init, track, conversion } from "@adverfly/pixel";

init(YOUR_WORKSPACE_ID, {
  currency: "EUR",
  timezone: "Europe/Berlin",
  features: { abTesting: true, autocapture: true, webVitals: true },
});

track("added_to_cart", { sku: "A-1", value: 2990 });

conversion("purchase", {
  transaction_id: order.id,
  transaction_gross_revenue: 9900,   // minor units, so 99.00 EUR
  transaction_currency: "EUR",
  is_new_customer: 1,
});

Beträge werden immer in kleinster Währungseinheit übergeben. Die Plattform speichert und rechnet in Cent und konvertiert einmal, für die Anzeige.

Next.js

init tut beim Server-Rendering nichts, der Aufruf ist also überall sicher. Im App Router gehört er in eine Client-Komponente, die vom Root-Layout gemountet wird:

"use client";
import { useEffect } from "react";
import { init } from "@adverfly/pixel";

export function Analytics() {
  useEffect(() => {
    init(Number(process.env.NEXT_PUBLIC_ADVERFLY_ID), {
      currency: "EUR",
      features: { spaPageviews: true, errorTracking: true },
    });
  }, []);
  return null;
}

Im Pages Router steht derselbe Aufruf in pages/_app.tsx.

Schalte spaPageviews in jedem Framework ein, das clientseitig routet. Ohne das Flag meldet das Pixel einen Pageview pro Hard-Navigation und nichts für die Screens dazwischen.

API

FunktionWas sie tut
init(workspaceId, options?)Konfiguriert das Pixel und lädt es. Kann zweimal und während SSR aufgerufen werden
track(name, properties?)Ein Custom-Event
conversion(name, payload)Ein umsatztragendes Event. Beträge in kleinster Währungseinheit
pageview(properties?)Ein Pageview. Nur nötig, wenn spaPageviews aus ist
identify(customerId)Hängt deine Kundenkennung an die Events, die dieses Paket ab jetzt sendet. Sie wird so übertragen, wie du sie setzt, und erst serverseitig vor dem Speichern gehasht
captureException(error)Meldet einen abgefangenen Fehler bewusst
consentChanged(level?)Sagt dem Pixel, dass sich das Consent geändert hat, damit es neu bewertet und nachholt
survey(surveyId)Rendert eine Umfrage anhand ihrer ID
isBot()Ob dieser Browser als automatisiert gilt und deshalb nicht gezählt wird

Optionen

Alles aus der Feature-Referenz steht zur Verfügung, in camelCase:

init(YOUR_WORKSPACE_ID, {
  currency: "EUR",
  timezone: "Europe/Berlin",
  consentLevel: "marketing",
  customerId: "customer@example.com",
  trackQueryChanges: false,
  extraBotPatterns: ["mycrawler"],
  disableBotDetection: false,
  features: {
    widgets: false,
    abTesting: false,
    sessionReplay: false,
    heatmap: false,
    autocapture: false,
    webVitals: false,
    errorTracking: false,
    spaPageviews: false,
    loyalty: false,
  },
  scriptUrl: "https://sos-de-fra-1.exo.io/adv/advv4.01.js",
  skipScript: false,
  nonce: undefined,
  isDev: false,
});

Diese Optionen sind keine Feature-Flags:

  • scriptUrl richtet den Loader auf ein anderes Bundle. Nützlich, wenn das Pixel über deine eigene Domain geproxied wird, um Adblocker zu überstehen, die nach Hostname filtern.
  • skipScript konfiguriert das Pixel, ohne etwas einzubinden. Nützlich, wenn dein Theme das Install-Snippet bereits enthält und du nur die typisierten Aufrufe willst.
  • nonce wird auf das eingefügte Script-Tag gesetzt. Nötig auf jeder Website, deren Content Security Policy Nonces für script-src verwendet — was die meisten Next.js-Apps mit strikter Policy tun. Ohne das Attribut blockiert der Browser das Tag und das Pixel lädt nie.

Wenn du das Pixel hinter einem Consent gatest, rufe init erst nach der Einwilligung auf — oder rufe es sofort mit consentLevel: "denied" auf und später consentChanged("marketing"). Die zweite Variante ist meist die bessere: Das Pixel ist schon geladen, wenn die Einwilligung kommt, es geht also nichts durch die Ladezeit des Skripts verloren.

init(YOUR_WORKSPACE_ID, { consentLevel: "denied" });

onConsent((granted) => consentChanged(granted ? "marketing" : "denied"));

Reihenfolge

Aufrufe vor init gehen nicht verloren. Sie landen in der Queue in window.adverfly und laufen in ihrer ursprünglichen Reihenfolge, sobald das Bundle da ist. Ein Install-Snippet, das bereits in deinem Theme steckt, wird weiterverwendet statt ersetzt — Paket und Snippet können während einer Migration also nebeneinander laufen.

Personalization SDK

Headless JS-SDK — Adverfly-Widgets mit eigenen Komponenten rendern

Das @adverfly/sdk JavaScript-Paket holt für jeden Besucher die passende Variante und du renderst sie wie du willst — eigene React/Vue/Svelte-Komponenten, server-gerenderte HTML, sogar Mobile-Apps.

Der Standard-Pixel funktioniert weiterhin als No-Code-Lösung. Das SDK ist für:

  • Pixelgenaue Brand-Kontrolle — kein iframe, keine CSS-Overrides
  • Server-Side / Edge — Cloudflare Workers, Vercel Edge, Next.js Server Components
  • Custom UI — Inline-Blocks, Mobile Screens, alles jenseits von Popup/Banner/Toast/Countdown
  • Strikte Typen — dein config-Shape wird zum TypeScript-Generic

Installation

Drei Wege, gleicher Code — wähle was zu deinem Stack passt.

Vanilla <script> (kein Build-Schritt)

Request
<script src="https://cdn.jsdelivr.net/npm/@adverfly/sdk/dist/adverfly.iife.js"></script>
<script>
  const adv = new Adverfly({ workspaceId: 188334 });
  /* `Adverfly` ist jetzt eine globale Klasse — kein Module-System nötig. */
</script>

In Production die Version pinnen: @adverfly/sdk@0.1.0/dist/adverfly.iife.js.

ES-Module im Browser

Request
<script type="module">
  import { Adverfly } from "https://cdn.jsdelivr.net/npm/@adverfly/sdk/dist/index.mjs";
  const adv = new Adverfly({ workspaceId: 188334 });
</script>

npm (Build-Pipelines, Node, SSR)

Request
npm install @adverfly/sdk
import { Adverfly } from "@adverfly/sdk";

Funktioniert in Browsern und Node 18+. Keine Peer-Dependencies. Bundle ~6 KB minified im IIFE-Build, ~12 KB als ESM-Build.

Quick Start

Request
import { Adverfly } from "@adverfly/sdk";

const adv = new Adverfly({ workspaceId: 188334 });

await adv.identify({ email: "user@example.com" });

adv.setContext({
  cart_value: 49.9,
  last_viewed_creatives: ["sku_a", "sku_b"],
});

const variant = await adv.personalize({ trigger: "exit_intent" });

if (variant) {
  zeigeMeinPopup({
    title: variant.config.title,
    copy: variant.config.copy,
    onCtaClick: () => adv.click(variant.id),
    onDismiss: () => adv.dismiss(variant.id),
  });
  await adv.trackImpression(variant.id);
}

Konstruktor

ParameterTypeRequiredDescription
workspaceIdnumberRequiredDeine Adverfly-Workspace-ID.
apiUrlstringOptionalOverride für Self-Hosted oder Staging. Default: https://b.adverfly.com.
customerIdstringOptionalVor-identifizieren ohne identify() aufzurufen.
debugbooleanOptionalDecisions + Events in console.log spiegeln, getaggt mit [adverfly].
manualIdentitybooleanOptionalAuto-Anonymous-ID deaktivieren (für SSR / wenn du Identität selbst verwaltest).

Identität

await adv.identify({
  email: "user@example.com",   // SHA-256 gehasht (lowercase) — nie roh übertragen
  transactionId: "order_123",  // optional, für Post-Purchase-Trigger
});

Identität zurücksetzen (z. B. bei Logout):

adv.reset();

Personalize

const variant = await adv.personalize<MyConfigShape>({
  trigger: "exit_intent",
  surface: "widget",  // optional, default: "widget"
  context: { device_battery_low: true },  // wird oben auf den Session-Context gemerged
});
// { id, config, reason } | null

Stark typisierter Config:

Request
interface PopupConfig {
  title: string;
  copy: string;
  cta?: string;
  cta_url?: string;
  image_url?: string;
}

const variant = await adv.personalize<PopupConfig>({ trigger: "exit_intent" });
if (variant) {
  console.log(variant.config.title); // typisiert!
}

Tracking

Alle Tracking-Events landen in ClickHouse pixel_events — gleiche Tabelle wie der Standard-Pixel, joinbar via customer_id (gehasht) für Conversion-Attribution.

CodeDescription
trackImpression(variantId)Widget wurde gerendert.
click(variantId, props?)User klickte CTA. props kann cta_url enthalten.
dismiss(variantId)User hat manuell geschlossen.
autoDismissed(variantId, reason?)Timer abgelaufen (Countdown, Toast).
success(variantId, props?)Goal erreicht. Wenn props.email gesetzt, automatisch gehasht vor Versand.

Events

Subscriben für Analytics, Debugging oder Custom-Render-Hooks.

Request
const off = adv.on("decision", ({ trigger, variant }) => {
  console.log(`[${trigger}] →`, variant?.id ?? "no match");
});

/* Returns Unsubscribe-Funktion */
off();

Verfügbare Events: decision, impression, click, dismiss, auto_dismissed, success, error.

Server-Side / Edge

Funktioniert überall wo fetch + crypto.subtle existieren (Node 18+, Bun, Cloudflare Workers, Vercel Edge).

Request
/* Cloudflare Worker — server-render einen personalisierten Hero-Block */
export default {
  async fetch(request: Request) {
    const adv = new Adverfly({
      workspaceId: 188334,
      manualIdentity: true,  // wir verwalten Identität selbst
    });
    await adv.identify({ customerId: getCookieUserId(request) });

    const variant = await adv.personalize({
      trigger: "ssr_hero",
      context: { country: request.cf?.country },
    });

    return new Response(renderHero(variant?.config), {
      headers: { "content-type": "text/html" },
    });
  },
};

Privacy

  • Emails werden clientseitig gehasht (SHA-256, lowercase + getrimmt) bevor irgendein Network-Call passiert.
  • Keine Cookies vom SDK gesetzt. Anonymous-IDs in localStorage; Opt-out via manualIdentity: true.
  • CORS-clean. POST + JSON, keine Preflight-Überraschungen.

Siehe auch

Heat Maps & Session Replays

Enable click/scroll capture and session recording in the tracking pixel

The v4 tracking pixel can capture on-page interactions for the Heat Maps app and record full sessions for the Session Replays app. Both are off by default and enabled per site with one flag each in your pixel snippet.

Heat Maps

<script>
  window.adverfly = window.adverfly || [];
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.activate_heatmap = true;
</script>

What gets captured:

  • Clicks — position (relative to document size, so all viewports aggregate correctly), the clicked element's selector, and up to 60 characters of its text.
  • Scroll depth — one max-depth summary per pageview.

Events are batched client-side and sent through the regular beacon endpoint. SPA route changes (pushState / popstate) are handled automatically.

Session Replays

<script>
  window.adverfly = window.adverfly || [];
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.activate_session_replay = true;

  /* Optional tuning */
  window.adverfly.replay_config = {
    sample_rate: 0.25,              /* record 25% of sessions (only when the app has no setting) */
    mask_selectors: [".account-menu"], /* extra elements whose text is masked */
    max_duration_s: 1800,           /* hard stop per session (default 30 min) */
  };
</script>

Sampling without touching your site

You don't need sample_rate in the snippet. The Recording setting in the Session Replays app (100 % down to 1 % of sessions) is read by the pixel directly and reaches new visitor sessions within a few minutes, with no theme change. Without a saved setting 10 % of sessions are recorded — the default takes effect from a site's first recording on. A saved Recording setting always wins over a sample_rate in the snippet; the snippet value only applies while the app has none.

The recorder is built on rrweb — the open-source engine behind most session-replay products. It is lazy-loaded only when recording actually starts, takes a DOM snapshot, then streams mutations, mouse movement, clicks, scrolls, and viewport changes in chunks to api.adverfly.com/replays/v1.

Diagnostics

Alongside the DOM, the recorder captures the three things that explain why a session went wrong. They appear in the player's Console and Network tabs and drive the "Errors" and "Frustration" filters.

CapturedWhat is storedWhat is never stored
JavaScript errorsMessage, error source, stack — each truncated and stripped of query stringsLocal variables, DOM state, anything an error object carries beyond its message
console.error / console.warnUp to four arguments, strings onlyObjects and arrays are recorded as [object], never serialised
Network requestsMethod, URL path, status code, durationRequest bodies, response bodies, headers, cookies, tokens
Page focusThat the visitor switched tab or window, and whenWhich tab, which site, anything about it

Your own console.error calls still run untouched — the original is always invoked first, and a failure inside our hook cannot break your page. Adverfly's own chunk uploads are excluded from the network log.

Page-focus markers exist for one reason: a click that opens a new tab or starts a download changes nothing on the page, and without them it would be misreported as a "dead click".

Privacy guarantees (always on)

  • Input, textarea, and select values are masked in the browser — real values never leave the page. Password and credit-card fields are fully redacted.
  • Elements with class="adv-no-capture" or data-adv-no-capture are dropped entirely from the recording.
  • Elements with class="adv-mask" or data-adv-mask keep their layout but have all text masked.
  • script, iframe, canvas, video, and audio elements are replaced by sized placeholders.
  • If a supported consent tool (Cookiebot, OneTrust, Usercentrics, TCF) reports consent as denied, the recorder does not start.

Recording both apps reuses the same session key, so heatmap data and replays line up per session.

Upsell on your storefront

Render Adverfly upsell prompts on product, cart, checkout and thank-you pages

Upsell rules you activate in the dashboard are published as a public JSON snapshot. Pixel v4 fetches that snapshot once per page, picks the highest-priority rule whose trigger matches the context you give it, renders the prompt, and reports impressions, accepts and declines back to your workspace. Changes go live within about a minute.

The pixel does everything except one step: it cannot add a line item to your cart. That handoff is described in Accepting an offer.

1. Turn the module on

Off by default, so no shop starts showing prompts by accident.

Request
<script>
  window.adverfly = window.adverfly || [];
  advPxl("init", 8397799);
  window.adverfly.activate_upsell = true;
</script>

With this set, the Post-purchase placement renders on its own on any page that already carries a transaction_id — your thank-you page. Every other placement needs step 2, because only your storefront knows what is in the cart.

2. Tell the pixel what the shopper is looking at

Request
advPxl("upsell", "cart", {
  product_ids: ["8412990013", "8412990014"],
  cart_value_minor: 5900,
});
PlacementWhen to call
product_pageon a product detail page, with that product's id
cartwhen the cart or cart drawer renders or changes
checkouton the checkout page
post_purchaseautomatic when transaction_id is set
thank_youorder confirmation, if you want a second prompt

Context fields, all optional:

FieldTypeMeaning
product_idsstring[]what the shopper is viewing or has in the cart
cart_value_minornumbercart subtotal in minor units (cents)
langstringoverrides the detected language for translated copy
containerstring | Elementrender inline into this element instead of as an overlay

By default the prompt renders as an overlay in the position configured on the rule. To place it inline, either pass container, or drop a placeholder in your markup:

Request
<div data-adv-upsell="cart"></div>

Accepting an offer

The pixel hands the offer over and lets your storefront perform the actual cart mutation. Define a handler, or listen for the event:

Request
window.adverfly.upsell_add_to_cart = function (offer, rule) {
  /* offer = { product_id, product_name, price_minor, quantity, discount } */
  return fetch("/cart/add.js", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ id: offer.product_id, quantity: offer.quantity }),
  });
};
Request
document.addEventListener("adverfly:upsell:accept", function (e) {
  console.log(e.detail.offer, e.detail.rule);
});

If neither is present the accept is still tracked, but nothing lands in the cart — so wire one of them up before activating a rule.

Events

Every prompt reports through the normal event pipeline. These are what the Upsell app's take rate and added-value columns are computed from.

EventFires when
upsell_impressionthe prompt is rendered
upsell_clickthe shopper closes it via the ✕
upsell_acceptedthe shopper takes the offer
upsell_declinedthe shopper explicitly declines

Manual control

Request
/* Render a specific placement and get the matched rule back. */
window.advUpsell.render("checkout", { product_ids: ["8412990013"] })
  .then(function (rule) { console.log(rule); });

/* Inspect what is published, without matching. */
window.advUpsell.getRules("cart").then(function (rules) { console.log(rules); });

/* Dismiss whatever is on screen. */
window.advUpsell.close();

Server-rendered storefronts

If you render the prompt yourself — a Shopify theme app extension, headless middleware — read the same published rules over HTTP instead of loading the pixel:

Request
curl "https://api.adverfly.com/upsell/v1/rules?workspace_id=8397799&placement=checkout"

It serves the same snapshot the pixel reads, filtered to rules that are currently within their schedule, so the two paths can never disagree. You then own the matching and the event reporting.

Web Vitals & Error Tracking

Messen, was langsam ist, und erfassen, was kaputt ist — mit dem Pixel, das du schon hast

Zwei Flags machen aus dem Tracking-Pixel einen schlanken Performance- und Fehler-Monitor. Keines davon ersetzt ein spezialisiertes Monitoring-Tool, und keines soll das. Dafür liefern sie etwas, das ein separates Tool nicht kann: Ladezeiten und JavaScript-Fehler liegen in derselben Tabelle wie der Umsatz. „Der Checkout ist auf Mobile langsam“ und „die Conversion-Rate ist auf Mobile gefallen“ können damit dieselbe Abfrage sein.

Core Web Vitals

<script>
  window.adverfly = window.adverfly || [];
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.activate_web_vitals = true;
</script>

Ein web_vitals-Event pro Pageview, mit allem, was der Browser gemeldet hat:

MetrikWas sie misstGut
lcpLargest Contentful Paint, wann der Hauptinhalt erschienen istunter 2,5 s
clsCumulative Layout Shift, wie stark die Seite gesprungen istunter 0,1
inpInteraction to Next Paint, die schlechteste Reaktionszeitunter 200 ms
fcpFirst Contentful Paint, wann überhaupt etwas erschienen istunter 1,8 s
ttfbTime to First Byte, wie lange der Server gebraucht hatunter 800 ms

Zeiten sind ganze Millisekunden, cls hat drei Nachkommastellen, und pathname liegt bei, damit du nach Template statt nach URL gruppieren kannst.

Die Zahlen kommen aus dem PerformanceObserver des Browsers, ohne geladene Library. Ein Browser, der einen Entry-Typ nicht unterstützt, meldet schlicht eine Metrik weniger — Safari liefert also weniger Felder als Chrome.

Alles wird gebündelt und einmal gesendet, wenn die Seite verschwindet, über sendBeacon, damit das Verlassen den Versand nicht abbricht. Einen Timer gibt es bewusst nicht. Zwei dieser Metriken sind über den gesamten Besuch definiert: Ein Cookie-Banner, das das Layout nach sechs Sekunden verschiebt, oder ein Tap nach zwanzig Sekunden gehört in die Zahl. Früher zu senden würde stillschweigend einen besseren Wert melden, als der Besucher erlebt hat — auffallen würde es erst, wenn die Search Console widerspricht.

cls ist das schlechteste Fünf-Sekunden-Fenster an Verschiebungen, nicht die Summe über den Besuch, und inp zählt nur echte Interaktionen, damit ein langsamer Hover-Handler nicht als Reaktionszeit von jemandem gemeldet wird, der nie geklickt hat. Beide folgen Googles Definitionen, die Werte sind also mit Search Console und PageSpeed Insights vergleichbar.

Messwerte über fünfzehn Minuten werden verworfen. Ein so großer Wert ist ein Tab im Hintergrund oder ein Zeitsprung, keine Seite, die fünfzehn Minuten zum Rendern gebraucht hat — und ein einziger solcher Ausreißer ruiniert jeden Durchschnitt.

Was du damit anfängst

Schlüssle deine Pageviews nach pathname auf und sortiere nach lcp. Das langsamste Template mit relevantem Traffic ist der Ort, an dem Geld liegt. Vergleiche Mobile gegen Desktop, bevor du Seiten gegeneinander vergleichst: Der Unterschied zwischen beiden ist meist größer als der zwischen deinem besten und deinem schlechtesten Template.

Error Tracking

<script>
  window.adverfly = window.adverfly || [];
  advPxl("init", YOUR_WORKSPACE_ID);
  window.adverfly.activate_error_tracking = true;
</script>

Nicht abgefangene Fehler und unbehandelte Promise-Rejections kommen als exception-Events an, mit Typ, Meldung, gekürztem Stack, Quelle und pathname.

Warum das für einen Shop-Betreiber zählt, in einem Satz: Ein Checkout-Button, der in einem Browser eine Exception wirft, kostet stillschweigend Bestellungen, die Conversion-Rate fällt, nichts in der Analytics sagt warum — und schuld ist am Ende der Traffic.

Was verhindert, dass daraus eine Logmüllhalde wird:

  • Derselbe Fehler wird einmal pro Pageview gesendet. Der Fingerprint besteht aus Typ, Meldung und erstem Stack-Frame: Ein Handler, der bei jeder Mausbewegung wirft, kostet ein Event statt zehntausend.
  • Höchstens zehn verschiedene Fehler pro Pageview.
  • Cross-Origin-Skriptfehler werden verworfen. Der Browser meldet sie als Script error. ohne Datei, Zeile oder Stack — damit lässt sich nichts anfangen. Wenn du sie sehen willst, ergänze crossorigin="anonymous" an deinen eigenen Script-Tags und liefere sie mit einem passenden CORS-Header aus.
  • Fehler aus dem Pixel selbst werden verworfen — beurteilt wird das am Stack und nie an der Meldung. ReferenceError: advPxl is not defined, geworfen von deinem Theme, ist genau der Fehler, der beweist, dass unser Skript blockiert wurde; er erreicht dich also.
  • Automatisierter Traffic ist ausgeschlossen, wie überall sonst auch.

Einen Fehler selbst melden

Ein abgefangener Fehler, einer von dem du dich erholt hast, ist oft der interessantere. Melde ihn bewusst. Das braucht kein Flag: Der Aufruf in deinem Code ist das Opt-in. Bot-Filterung und das Limit pro Seite gelten weiterhin.

try {
  await submitOrder();
} catch (err) {
  advPxl("exception", err);
  showRetry();
}

Aus dem npm-Paket:

import { captureException } from "@adverfly/pixel";
captureException(err);

Was nie erfasst wird

Die Meldung, der Stack und der Pfad, sonst nichts. Keine lokalen Variablen, kein DOM-Zustand, keine Request- oder Response-Bodies, nichts, was ein Error-Objekt über seine Meldung hinaus mitbringt. Wenn deine Fehlermeldungen heute personenbezogene Daten enthalten, gehen die mit — entferne sie an der Stelle, an der der Fehler geworfen wird.

Beides zusammen

Kein Flag hängt vom anderen ab, und beide sind standardmäßig aus. Session Replay erfasst Fehler ebenfalls, innerhalb der Aufzeichnung, wo sie eine einzelne Session erklären. Diese Events sind zum Aggregieren da: welches Template, welcher Browser, wie oft — und ob es sich mit dem Umsatz bewegt.

Datenqualität

Wie das Pixel Bots aus deinen Zahlen hält und Events durch ein unzuverlässiges Netz bringt

Zwei Dinge entscheiden im Stillen darüber, ob man einem Dashboard trauen kann: ob der Traffic darin menschlich ist, und ob die Events, die ankommen sollten, auch wirklich angekommen sind. Beides erledigt das Pixel, beides ist standardmäßig an, und keines davon muss konfiguriert werden.

Bot-Filterung

Crawler, die JavaScript ausführen, sind von Kunden nicht zu unterscheiden, solange nichts anderes dagegen spricht. Sie erzeugen Pageviews, bekommen eine Visitor-ID, landen in A/B-Varianten und verzerren jede Kennzahl, die Besucher im Nenner hat.

Das ist kein theoretisches Problem. Auf dieser Plattform hat ein einziger „Besucher“ an einem Tag 10.949 von 11.886 Pageviews eines Shops erzeugt, und zwei weitere Shops hatten einen Crawler, der bis zu 46.386 Pageviews unter einer Visitor-ID produziert hat. Eine Conversion-Rate über solchem Traffic ist wertlos.

Deshalb entscheidet das Pixel, bevor die erste Anfrage den Browser verlässt, ob es mit einem Menschen spricht:

SignalWas es erkennt
User AgentSuchmaschinen-Crawler, SEO-Tools, Uptime-Monitore, AI-Crawler, HTTP-Bibliotheken, Headless Chrome
navigator.webdriverJeden Browser unter Automatisierungssteuerung
Injizierte GlobalsSelenium, Puppeteer, Playwright, Nightmare und Verwandte, auch bei gefälschtem User Agent
Headless-SignaturNull Plugins und keine Sprachen — eine Kombination, die ein echter Browser nie meldet

Ein erkannter Bot kostet eine Skript-Auswertung und sonst nichts. Keine Events, keine Session, keine A/B-Zuteilung, keine Aufzeichnung, keine Heatmap, kein Replay.

Die Prüfung ist bewusst konservativ. Ein False Positive löscht stillschweigend die komplette Journey eines echten Kunden, und das ist schlimmer, als einen Crawler durchzulassen. Jedes Signal muss deshalb etwas sein, das ein normaler Browser nie meldet. Null Plugins allein reicht nicht, weil Privacy-Browser sie entfernen — es zählt nur zusammen mit einer leeren Sprachliste.

Eigene Muster ergänzen

Lasttests, ein Monitoring-Skript oder ein interner Crawler, der sich in der Standardliste nicht zu erkennen gibt:

<script>
  window.adverfly.extra_bot_patterns = ["mycrawler", /loadtest/i];
</script>

Strings werden ohne Rücksicht auf Groß- und Kleinschreibung gegen den User Agent geprüft; reguläre Ausdrücke werden so verwendet, wie sie angegeben sind.

Abschalten

<script>
  window.adverfly.disable_bot_detection = true;
</script>

Tu das nur, um ein Problem nachzustellen. Bleibt es in Produktion an, steckt Crawler-Traffic wieder in jeder Kennzahl, die du berichtest.

Einen Browser prüfen

window.adverflyIsBot()   // true when this browser is not being counted

Hilfreich, wenn eine Testumgebung keine Daten meldet: Ein automatisierter Browser tut dann genau das, was er soll — nämlich nichts.

Zustellung — und ihre eine harte Grenze

Eine Tracking-Anfrage scheitert aus banalen Gründen: ein Tunnel, eine wacklige Verbindung, ein Rate Limit, eine Seite, die mitten in der Anfrage entladen wird. Jeder dieser Fälle ist eine Conversion, die in einem Report fehlt, den niemand hinterfragt — weil eine fehlende Zeile wie ein ruhiger Tag aussieht.

Die naheliegende Antwort ist, sie einfach noch einmal zu senden. Warum wir das nur in eng begrenzten Fällen tun, gehört klar gesagt, denn es bestimmt alles Weitere: Die Ingestion dedupliziert nicht. Jedes Event wird beim Eintreffen auf dem Server gestempelt und mit einem Schlüssel versehen; eine zweite Zustellung desselben Events ist deshalb eine zweite Zeile — bei einem Kauf ein zweiter Verkauf. Aufgeblähter Umsatz, den niemand erklären kann, ist deutlich schlimmer als ein fehlender Pageview.

Eine Anfrage wird also nur wiederholt, wenn der Fehler beweist, dass sie nie verarbeitet wurde:

FehlerWiederholt?Warum
429, 502, 503, 504Ja, zweimal, mit BackoffDie Edge hat die Anfrage abgewiesen. Sie kann also nicht schon existieren
500NeinDer Handler kann das Event geschrieben haben und erst danach gescheitert sein
TimeoutNeinDie Anfrage kann verarbeitet worden sein und nur die Antwort ging verloren
Netzwerkfehler, Browser onlineNeinDieselbe Unklarheit
Netzwerkfehler, Browser offlineWird eingereiht, wenn du die Queue aktiviert hastDass der Browser sich selbst als offline meldet, ist der Beweis, dass das Event nicht angekommen ist

Unload-sicheres Senden deckt den anderen häufigen Verlust ab: Events, die beim Verlassen der Seite entstehen, die gebündelten Web Vitals eingeschlossen, gehen über sendBeacon raus, das der Browser auch nach dem Verschwinden der Seite abschließt. Eine gewöhnliche Anfrage wird an dieser Stelle abgebrochen.

Die Offline-Queue

Standardmäßig aus. Eine Zeile schaltet sie ein:

<script>
  window.adverfly.activate_offline_queue = true;
</script>

Ein Event, das gescheitert ist, während der Browser offline war, wird dann in localStorage geparkt und beim nächsten Pageview des Besuchers erneut gesendet. Die Queue fasst höchstens 25 Events und 64 KB, verwirft alles, was älter als 24 Stunden ist, und wirft gewöhnliche Events vor Conversions raus, wenn sie Platz schaffen muss — ein Schwung Offline-Surfen kann also keinen Kauf verdrängen.

Sie ist opt-in, weil ein Replay seinen Preis hat. Ein erneut gesendetes Event bekommt den Zeitstempel seiner Ankunft, nicht den seines Entstehens. Ein Kauf, der um 22:00 Uhr geparkt und am nächsten Morgen um 09:00 Uhr nachgeliefert wird, landet am falschen Tag, in einer Session ohne Ad-Klick: Der Umsatz verschiebt sich und die Attribution stimmt nicht. Das ist eine andere Art von falsch, als das Event schlicht zu verlieren — deshalb ist es deine Entscheidung und nicht unsere.

Schalte sie ein, wenn es wichtiger ist, das Event zu retten, als das Datum exakt zu haben. Für die meisten Shops heißt das: einschalten, wenn du an Menschen in Zügen verkaufst.

Was nichts davon löst

Adblocker und Tracking-Schutzlisten blockieren die Anfrage, bevor das Pixel irgendetwas tun kann, und kein clientseitiger Mechanismus ändert daran etwas. Das Bundle und den Beacon über deine eigene Domain zu proxien, ändert es. Das ist die mit Abstand größte behebbare Lücke jeder clientseitigen Messung und je nach Publikum typischerweise 10 bis 30 Prozent der Events wert. Melde dich beim Support, wenn du das einrichten möchtest.

Virtual try-on

Optional photo previews, email requests and controlled storefront experiments

Virtual try-on is configured in Collect → Try-on. It edits a visitor's photo using a product reference; it is not a live camera filter. The first recipes are eyelashes and press-on nails.

Enable Pixel v5

After deploying v5, replace the existing v4 script URL with https://sos-de-fra-1.exo.io/adv/advv5.01.js. Keep exactly one pixel script on the page; adding the flag to a v4 installation does not enable Try-on.

Set the flag in your existing Adverfly snippet before Pixel v5 loads:

window.adverfly = window.adverfly || [];
window.adverfly.activate_try_on = true;

The flag alone does not activate the experience. The workspace must also have an enabled configuration, current eligible product coverage and approved photo consent copy. Configure these in the app. The backend must be deployed first.

Place an optional anchor next to your product variants:

<div data-adverfly-tryon></div>

Alternatively configure a selector in the app. Without an anchor or selector, the module attaches to the product's add-to-cart form. Only eligible product pages show the button. Images are generated after an explicit photo-consent checkbox. Marketing signup is a separate, optional checkbox after the result.

Product setup

Select collection handles, tags or all products, save while disabled, then run Refresh coverage. When collection and tag filters are both set, both must match. Eyelashes use the second Shopify product image. An optional namespace.key metafield overrides the reference; a missing override makes that product ineligible. Inspect references manually before enabling the shop.

Experiments and reporting

Create a draft in Try-on and start it in A/B Testing. The existing storefront is the control; the other group receives Try-on and optional email capture. Assignment is stable per visitor. Exposure is recorded for both groups only on eligible product pages. Purchase conversion uses the existing experiment engine.

The module emits tryon_opened, tryon_consented, tryon_generated, tryon_failed, tryon_switched_style, tryon_add_to_cart and tryon_lead_captured. These events contain no photo, email address or upload URL.

Contacts, limits and rollout

Email requests are listed in the app with their consent version and unconfirmed status. This feature does not send a result email or subscribe contacts to a newsletter automatically. Verify your confirmation integration separately.

The app shows daily, session and monthly limits and server-configured pricing. The monthly fee includes zero images. Successful new images incur an image charge; technical failures and downloads do not incur another image charge.

Launch requires customer-approved photo and marketing copy, privacy information, processing-location review and a staged deletion test. Photos and results are private and have a 23-hour access deadline; scheduled deletion runs every 15 minutes, with S3 Lifecycle as a backstop. Closing the dialog requests deletion. Disable the workspace configuration to stop new jobs; cleanup continues.