XunL-Experimente: Seiten visuell bearbeiten, Besucher konsistent zuordnen, akzeptierte Anfragen auf dem Server bestätigen und Berichte mit Konfidenzintervallen und einem klaren Entscheidungsstatus erhalten.
Wählen Sie, wie Sie eine Verbindung herstellen möchten
Beide nutzen die gleiche Site-Verbindung, dieselben Experimente und Berichte; Lediglich Installation und Anfragebestätigung unterscheiden sich.
| JS SDK | WordPress-Plugin | |
|---|---|---|
| Websites | Jede Website: Shopify, Webflow, Wix, Squarespace, Tag-Manager, benutzerdefinierte Websites | WordPress 6.2 oder höher |
| Installieren | Fügen Sie eine Skriptzeile zum gemeinsamen Kopf hinzu | Laden Sie die Plugin-ZIP-Datei hoch und geben Sie die Verbindungsdetails ein |
| Seiten-, Klick- und benutzerdefinierte Ereignisziele | Unterstützung | Unterstützung |
| Akzeptiertes Anfrageziel | Von Ihrem Server signiert (Node.js-, Python- und PHP-Beispiele unten) | Automatisch für Contact Form 7, Elementor Pro, WPForms und Gravity Forms |
Jede Website: Installieren Sie das JS SDK
- Melden Sie sich bei XunL an, fügen Sie die Website unter „Sites“ hinzu und erstellen Sie dann eine Verbindung unter „A/B-Experimente → Sites & Installation“.
- Kopieren Sie das Skript, das die Konsole anzeigt, und platzieren Sie es so früh wie möglich im Header, der von jeder Seite gemeinsam genutzt wird. Ersetzen Sie SITE_PUBLIC_KEY im Beispiel durch den öffentlichen Schlüssel Ihrer Site.
- Verbinden Sie die Besuchereinwilligung (unten) und wählen Sie dann in der Konsole Verbindung prüfen aus.
<script defer src="https://www.xunl.net/js/xunl-experiments-sdk.js" data-xunl-key="SITE_PUBLIC_KEY" data-xunl-origin="https://www.xunl.net" data-xunl-consent="pending"></script>Das Skript liest nur die öffentliche Experimentkonfiguration; Geben Sie das Connector-Geheimnis niemals in eine Webseite ein. Rufen Sie in Single-Page-Apps XunLExperiments.refresh() auf, nachdem Sie Routen mit History.pushState geändert haben.
Wo kann es auf jeder Plattform hinzugefügt werden?
- Shopify
- Online-Shop → Themes → Code bearbeiten → layout/theme.liquid, direkt vor dem schließenden Head-Tag einfügen. Der Checkout kann nur bei Shopify Plus bearbeitet werden. Legen Sie daher vor dem Checkout Ziele auf den Seiten fest.
- Webflow
- Site-Einstellungen → Benutzerdefinierter Code → Kopfcode, einfügen und veröffentlichen. Erfordert einen kostenpflichtigen Lageplan.
- Wix
- Einstellungen → Erweitert → Benutzerdefinierter Code → Benutzerdefinierten Code hinzufügen, wählen Sie „Alle Seiten“ und „Kopf“. Erfordert einen Premium-Plan mit einer verbundenen Domain.
- Squarespace
- Einstellungen → Erweitert → Code-Injection → Header. Erfordert den Businessplan oder höher.
- Google Tag Manager
- Erstellen Sie ein benutzerdefiniertes HTML-Tag, fügen Sie das Skript ein und lösen Sie es auf „Alle Seiten“ oder „Initialisierung – Alle Seiten“ aus. Das Laden über einen Tag-Manager erfolgt später als das Schreiben in den Kopf, sodass visuelle Änderungen eher flackern.
- Benutzerdefinierte Websites, React, Next.js
- Fügen Sie es in den HTML-Vorlagenkopf ein, z. B. das Root-Layout oder _document in Next.js oder index.html in Vue- und React-Projekten.
Besuchereinwilligung verknüpfen
Standardmäßig data-xunl-consent="pending": Vor der Einwilligung greift das SDK nicht in den Browserspeicher ein, erstellt keine Besucher-ID und sendet keine Ereignisse. Rufen Sie über den Rückruf Ihres Cookie-Banners einen der folgenden Schritte auf:
// After the visitor accepts analytics cookies in your consent banner
XunLExperiments.consent(true);
// If they decline or withdraw consent
XunLExperiments.consent(false);
// Or, without touching the SDK directly (also works before it has started)
window.dispatchEvent(new CustomEvent('xunl:consent', { detail: { granted: true } }));Wenn Ihre Website dort, wo sie betrieben wird, keine Einwilligung benötigt, setzen Sie „data-xunl-consent“ im Skript auf „Gewährt“. Ob eine Einwilligung erforderlich ist, hängt von den Regeln ab, wo sich Ihre Website und Besucher aufhalten.
Legen Sie Experimentziele fest
Jedes Experiment hat ein Hauptziel, das vor dem Start festgelegt wird; Das Urteil verwendet nur das Hauptziel und andere Ziele dienen als Referenz.
| Ziel | Counts when | Beispiel |
|---|---|---|
| Seiten | Der Besucher erreicht eine URL, beispielsweise eine Dankesseite | https://www.ssevening.com/thank-you/ |
| Klicken Sie | Ein Klick auf ein Element, das mit einem CSS-Selektor übereinstimmt | #quote-button |
| Benutzerdefiniertes Ereignis | Von Ihrem Code mit Track gemeldet | XunLExperiments.track('quote_started') |
| Akzeptierte Anfrage | Ihr Server bestätigt, dass die Anfrage gespeichert oder gesendet wurde; siehe nächsten Abschnitt | — |
Anzahl akzeptierter Anfragen: Serverbestätigung
Auf Websites, die nicht WordPress sind, zählen Sie akzeptierte Anfragen in drei Schritten. Senden Sie nur, nachdem die Anfrage tatsächlich gespeichert oder gesendet wurde, niemals bei Klicks, Validierungsfehlern oder Spam.
Schritt 1: Lassen Sie Formulare den Experimentkontext tragen
Fügen Sie data-xunl-forms="all" zum Skript hinzu (oder einen CSS-Selektor, um nur Anfrageformulare abzudecken). Eingereichte Formulare enthalten dann ein verstecktes xunl_context-Feld.
<script defer src="https://www.xunl.net/js/xunl-experiments-sdk.js" data-xunl-key="SITE_PUBLIC_KEY" data-xunl-origin="https://www.xunl.net" data-xunl-consent="pending" data-xunl-forms="all"></script>Fügen Sie bei Formularen, die mit Fetch oder AJAX gesendet werden, den Kontext selbst zur Anfrage hinzu:
const body = new FormData(form);
const ctx = window.XunLExperiments && XunLExperiments.inquiryContext();
if (ctx) body.set('xunl_context', JSON.stringify(ctx));
await fetch('/api/inquiry', { method: 'POST', body });Schritt 2: Unterschreiben und senden, nachdem Ihr Server die Anfrage gespeichert hat
Das Connector-Geheimnis wird einmalig angezeigt, wenn Sie die Verbindung erstellen; Behalten Sie es in der Serverumgebungsvariablen XUNL_CONNECTOR_SECRET. Verwenden Sie Ihre eigene Anfragenummer als SubmissionId; Das erneute Absenden derselben Anfrage wird nicht zweimal gezählt.
// Node 18+. Call after your server has saved or sent the inquiry.
const crypto = require('node:crypto');
const XUNL = 'https://www.xunl.net', KEY = 'SITE_PUBLIC_KEY', SECRET = process.env.XUNL_CONNECTOR_SECRET;
async function confirmInquiry(xunlContext, submissionId) {
let ctx; try { ctx = JSON.parse(xunlContext || 'null'); } catch { return false; }
if (!ctx || ctx.preview || !ctx.visitorId || !(ctx.assignments || []).length) return false;
const body = JSON.stringify({ eventId: crypto.randomUUID(), submissionId, visitorId: ctx.visitorId,
acceptedAt: new Date().toISOString(), assignments: ctx.assignments, status: 'accepted' });
const path = '/x/ab/v1/sites/' + KEY + '/conversions';
for (let attempt = 0; attempt < 3; attempt++) { // retries keep the body, sign again
const ts = String(Math.floor(Date.now() / 1000)), nonce = crypto.randomUUID();
const digest = crypto.createHash('sha256').update(body).digest('hex');
const sig = crypto.createHmac('sha256', SECRET).update(`v1\nPOST\n${path}\n${ts}\n${nonce}\n${digest}`).digest('hex');
const res = await fetch(XUNL + path, { method: 'POST', body, redirect: 'error', headers: { 'Content-Type': 'application/json',
'X-XunL-Timestamp': ts, 'X-XunL-Nonce': nonce, 'X-XunL-Signature': 'v1=' + sig } }).catch(() => null);
if (res && res.ok) return true;
if (res && res.status !== 409 && res.status < 500) return false;
await new Promise(r => setTimeout(r, 2000 * (attempt + 1)));
}
return false;
}
module.exports = { confirmInquiry };# Python 3.8+. Call after your server has saved or sent the inquiry.
import hashlib, hmac, json, os, time, uuid, urllib.error, urllib.request
from datetime import datetime, timezone
XUNL, KEY, SECRET = 'https://www.xunl.net', 'SITE_PUBLIC_KEY', os.environ['XUNL_CONNECTOR_SECRET']
def confirm_inquiry(xunl_context, submission_id):
try:
ctx = json.loads(xunl_context or 'null')
except ValueError:
return False
if not ctx or ctx.get('preview') or not ctx.get('visitorId') or not ctx.get('assignments'):
return False
body = json.dumps({'eventId': str(uuid.uuid4()), 'submissionId': submission_id, 'visitorId': ctx['visitorId'],
'acceptedAt': datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z'),
'assignments': ctx['assignments'], 'status': 'accepted'}).encode()
path = f'/x/ab/v1/sites/{KEY}/conversions'
for attempt in range(3): # retries keep the body, sign again
ts, nonce = str(int(time.time())), str(uuid.uuid4())
canonical = f'v1\nPOST\n{path}\n{ts}\n{nonce}\n{hashlib.sha256(body).hexdigest()}'
sig = hmac.new(SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(XUNL + path, data=body, method='POST', headers={'Content-Type': 'application/json',
'X-XunL-Timestamp': ts, 'X-XunL-Nonce': nonce, 'X-XunL-Signature': 'v1=' + sig})
try:
with urllib.request.urlopen(req, timeout=10):
return True
except urllib.error.HTTPError as e:
if e.code != 409 and e.code < 500:
return False
except OSError:
pass
time.sleep(2 * (attempt + 1))
return False<?php // PHP 7.4+ with cURL. Call after your server has saved or sent the inquiry.
function xunl_uuid(): string { $b = random_bytes(16); $b[6] = chr(ord($b[6]) & 0x0f | 0x40); $b[8] = chr(ord($b[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4)); }
function xunl_confirm_inquiry(string $xunlContext, string $submissionId): bool {
$xunl = 'https://www.xunl.net'; $key = 'SITE_PUBLIC_KEY'; $secret = getenv('XUNL_CONNECTOR_SECRET');
$ctx = json_decode($xunlContext, true);
if (!is_array($ctx) || !empty($ctx['preview']) || empty($ctx['visitorId']) || empty($ctx['assignments'])) return false;
$body = json_encode(['eventId' => xunl_uuid(), 'submissionId' => $submissionId, 'visitorId' => $ctx['visitorId'],
'acceptedAt' => (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s.v\Z'),
'assignments' => $ctx['assignments'], 'status' => 'accepted'], JSON_UNESCAPED_SLASHES);
$path = '/x/ab/v1/sites/' . $key . '/conversions';
for ($attempt = 0; $attempt < 3; $attempt++) { // retries keep the body, sign again
$ts = (string) time(); $nonce = xunl_uuid();
$sig = hash_hmac('sha256', "v1\nPOST\n$path\n$ts\n$nonce\n" . hash('sha256', $body), $secret);
$ch = curl_init($xunl . $path);
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "X-XunL-Timestamp: $ts", "X-XunL-Nonce: $nonce", "X-XunL-Signature: v1=$sig"]]);
curl_exec($ch); $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($code >= 200 && $code < 300) return true;
if ($code > 0 && $code !== 409 && $code < 500) return false;
sleep(2 * ($attempt + 1));
}
return false;
}Schritt 3: der API-Vertrag
- Adresse
- POST https://www.xunl.net/x/ab/v1/sites/{SITE_PUBLIC_KEY}/conversions
- Kopfzeilen
- Inhaltstyp: application/json · X-XunL-Timestamp · X-XunL-Nonce · X-XunL-Signature: v1=<hex>
- Zeichenfolge zum Signieren
- v1\nPOST\n/x/ab/v1/sites/{key}/conversions\n{timestamp}\n{nonce}\n{sha256(body)}
- Algorithmus
- HMAC-SHA256 (Connector-Geheimnis, zu signierende Zeichenfolge) → Kleinbuchstabe hexadezimal
- Körper
- {EventId, SubmissionId, VisitorId, AcceptedAt, Assignments, Status: „Accepted“}
- Zeit und Nonce
- Unix-Sekunden innerhalb von 300 s Serverzeit; Nonce ist eine UUID in Kleinbuchstaben, die für jede Anfrage neu ist.
- Antworten
- 200 akzeptiert oder dupliziert; 401 schlechte Signatur oder Zeit; 409 Die Browserzuweisung ist noch nicht eingetroffen. Signieren Sie daher denselben Text erneut und versuchen Sie es später noch einmal. Versuchen Sie es nicht erneut mit anderen 4xx.
WordPress-Plugin
- Laden Sie das Plugin ZIP unter A/B-Experimente → Websites & Installation herunter, gehen Sie dann in WordPress zu Plugins → Neu hinzufügen → Plugin hochladen und aktivieren Sie es.
- Geben Sie in WordPress → Einstellungen → XunL-Experimente die XunL-Adresse, den öffentlichen Schlüssel der Site und das Connector-Geheimnis ein.
- Ordnen Sie die Anfrageformulare so zu, dass sie als Typ:Formular-ID gelten, zum Beispiel cf7:123. Nicht aufgeführte Formulare werden niemals gesammelt.
- Verbinden Sie die Besuchereinwilligung und überprüfen Sie dann die Verbindung in der Konsole. Das Plugin enthält bereits das SDK, also fügen Sie das Skript nicht erneut hinzu.
Überprüfen Sie die Verbindung
- Akzeptieren Sie Cookies auf Ihrer Website, öffnen Sie die Browser-Entwicklerkonsole und führen Sie den folgenden Befehl aus; „consentStatus“ sollte gewährt werden.
- Zurück in der Konsole wählen Sie Verbindung prüfen und bestätigen, dass Ereignisse und Zuweisungen zunehmen.
- Öffnen Sie die Experimentseite mehrmals in privaten Fenstern und bestätigen Sie, dass beide Versionen korrekt angezeigt und übermittelt werden.
XunLExperiments.diagnostics()
// { version, consentStatus, visitorId, assignments, pendingEvents, lastError, ... }Häufig gestellte Fragen
Kann ich es auf einer Website verwenden, die nicht WordPress ist?
Ja. Fügen Sie das JS SDK-Skript zu Ihrem Shared Head hinzu, um visuelle und Split-URL-Experimente durchzuführen und Seiten-, Klick- und benutzerdefinierte Ereignisziele zu messen. Akzeptierte Anfragen werden von Ihrem eigenen Server signiert und versendet; Das WordPress-Plugin erledigt diesen Schritt einfach für Sie.
Warum müssen Anfragen vom Server bestätigt werden?
Der Browser sieht nur, dass auf „Senden“ geklickt wurde, was auch Validierungsfehler, Spam und Duplikate einschließt. Wenn Ihr Server die Anfrage signiert, nachdem sie tatsächlich gespeichert oder gesendet wurde, stimmt der Bericht mit dem überein, was die Verkäufe erhalten.
Verlangsamt es die Seite oder verursacht es Flimmern?
Das Skript wird asynchron geladen und behält die Originalseite bei, wenn die Konfiguration fehlschlägt oder eine Zeitüberschreitung auftritt. Visuelle Änderungen werden wirksam, sobald die Konfiguration eintrifft. Wenn Sie das Skript also frühzeitig platzieren und die Änderungen gering halten, wird das Flimmern reduziert. Für eine vollständige Neugestaltung verwenden Sie eine geteilte URL, um Besucher auf die neue Seite zu leiten.
Wird vor der Zustimmung der Besucher etwas aufgezeichnet?
Nein. Standardmäßig wartet das SDK: bis XunLExperiments.consent(true) aufgerufen wird oder ein xunl:consent-Ereignis ausgelöst wird, greift es nicht auf den Browserspeicher zu, erstellt keine Besucher-ID und sendet kein Experimentereignis. Durch den Widerruf der Einwilligung werden die lokalen Aufzeichnungen gelöscht.
Was ist mit Single-Page-Apps (React, Vue)?
Die Vorwärts- und Vorwärtsnavigation wird automatisch wieder abgeglichen. Nachdem Sie die Routen mit „history.pushState“ geändert haben, rufen Sie XunLExperiments.refresh() einmal auf, damit das SDK neu bewertet, welche Experimente für die neue URL gelten.