Guided onboarding
After sign-up and creating a project, X3 Growth Intelligence guides you through connecting every
tool, one after another. Each step shows a live status, offers a function test and can be resumed
at any time. Claude Code can drive the same onboarding through the MCP server x3.
Overview
The wizard lives at /onboarding?org=<org>&project=<project> in the dashboard. It has seven
working steps and a summary screen ("Fertig"):
| # | Step | On screen | Required |
|---|---|---|---|
| 1 | project | "Organisation & Projekt" | yes |
| 2 | install | "Tracking einbauen" | yes |
| 3 | goals | "Ziele & Funnels" | recommended (skippable) |
| 4 | ads | "Werbekonten" | optional |
| 5 | conversions | "Conversions senden" | optional |
| 6 | ai | "Claude verbinden" | optional |
| 7 | team | "Team einladen" | optional |
done | "Fertig" – summary of every integration | – |
Moving on is never blocked: "Weiter" always works, "Überspringen" is offered for optional steps,
and the status stays honest. Re-open the onboarding any time under Projekt → Einstellungen →
Setup-Status (/<org>/<project>/settings/setup); every row links back into its step, and skipped
steps can be resumed ("Wieder aufnehmen"). While the required steps are incomplete, the project
overview shows a slim banner ("Einrichtung: 3 von 7 Schritten erledigt – weiter").
Members who lack the permission for a step see it read-only with the chip "keine Berechtigung" – for example only organization admins can create keys or invite people.
How status is checked
Every step's state is derived from real data, not checkboxes: the API computes it on each
request (GET /v1/projects/{project}/setup/status). The only stored user input is skipped,
done (team only) and dismissed (banner).
| State | Chip | Meaning |
|---|---|---|
not_connected | "nicht verbunden" | nothing configured yet |
waiting | "wartet auf Daten" | configured, no proof from real data yet |
connected | "verbunden" | proven by real data within the freshness window |
error | "Fehler" | a concrete problem – always with a reason and a fix |
skipped | "übersprungen" | you skipped an optional step |
not_applicable | "nicht nötig" | e.g. conversions while no ad platform is in use |
A step is as bad as its worst required item (error > waiting > not_connected > connected).
Real data always wins over a skip: a skipped ad step with an active connection shows connected.
Every item carries a reason code (see Reason codes) with a text and a fix.
Freshness windows:
| Signal | Window |
|---|---|
| Tracking connected | an accepted pageview or event within 7 days |
| Goal or custom event "seen" | 24 hours |
| Ad spend synced | metrics within the last 3 days for an enabled account |
| UTM coverage | sessions of the last 14 days |
| Conversion upload proven | an upload (not a test) or a successful test within 30 days |
| Claude connected | an MCP call for the project within 30 days |
Test visits count for the setup checks (see Test visits and conversions), so the function tests turn steps green; they never count in reports, UTM coverage or upload counts.
Project
Name, website address, further domains, time zone, currency and allowed origins. Checks:
domains– at least one real (non-localhost) domain, elseonly_localhost.origins– every domain has anhttps://allowed origin, elseorigin_missingwith a one-click "Hinzufügen".timezone– a warningtimezone_utcwhen the project runs on UTC.
Allowed-origin edits read the current list and send the complete merged list – the tracking config replaces each top-level key as a whole.
Install tracking
Pick how the website is built (WordPress, Webflow, Shopify, Next.js, React, Astro, HTML, Google Tag Manager, Claude Code) or click "Weiß ich nicht" – X3 fetches the site's HTML through its SSRF guard and suggests the matching tab. Every code tab contains your publishable key already; Next.js, WordPress and server-hosted stacks offer the first-party proxy with a "Proxy-Schlüssel erzeugen" button. "Anleitung an Entwickler:in schicken" mails the instructions (never a secret) or invites the developer as an admin.
The live panel below the code polls the installation status every few seconds: first
pageview, hosts seen (allowed or rejected), SDK version, proxy use, consent share, custom events of
the last 24 h, the last event and the last website check. "Testbesuch senden" opens your site with
?x3_test=1 (the project's test parameter) – the events appear live and are marked as test
traffic. "Website jetzt prüfen" runs the installation check (snippet, key, origin, proxy, CSP,
events).
Typical fixes: no_pageview (publish the code, send a test visit),
origin_rejected (allow the domain only if it is yours),
proxy_key_derived, consent_never_granted.
Goals and funnels
Presets: "Formular abgeschickt" (form_submit, switches the forms plugin on), "Termin gebucht"
(booking, with a booking adapter: Calendly, thank-you page, postMessage, window event),
"WhatsApp-Klick", "Anruf-Klick", "E-Mail-Klick", "Kauf" (registers the purchase schema – needs
code), "Quiz abgeschlossen" and "Danke-Seite erreicht". Each card shows "Gesehen (24 h)". The
step is connected when at least one conversion goal was seen in 24 hours.
Optional: a standard funnel (landing → CTA click → your main goal) and the relevance preset "Standard (Lead-Website)", which forces your conversion goals to the top tier – previewed before it is applied.
Ad platforms
Connect Google Ads, Meta Ads, LinkedIn Ads and TikTok Ads one by one through Composio (hosted sign-in, then back to the wizard), select the ad accounts and X3 backfills 90 days of spend. X3 only reads – it never changes anything in your ad accounts. See Composio setup.
The step also shows the recommended UTM templates per platform with
copy buttons and where to paste them, and checks the UTM coverage of real paid clicks of the
last 14 days (utm_partial, utm_missing,
no_paid_traffic).
Conversions
Part A defines what is reported (conversion definitions, suggested from your goals). Part B configures where – Meta CAPI, LinkedIn CAPI, Google Ads (API or CSV feed) and TikTok. When step 4 has a connection for the platform, "Verbindung aus Schritt 4 verwenden" needs no token at all. Secrets are stored encrypted and never shown again.
"Test-Event senden" sends one synthetic conversion in the provider's test mode (Meta: test event
code; Google Ads: validate only; LinkedIn: configuration check) and shows the provider's answer –
see Test modes. A destination in test mode keeps the warning
mode_test until you click "Live schalten".
Part C creates a server key (events:write, leads:write, conversions:write) when your
server has to report or confirm conversions.
Connect Claude
Two cards: claude.ai (custom connector with the MCP URL, OAuth sign-in – choose only this project and "Nur lesen" for analysis) and Claude Code (secret key or keyless sign-in, see Claude Code prompts). The function test lists the last MCP calls for the project ("Claude Code · Schlüssel …", tool, ✓/✗); paste the prompt "Verbindung testen" into Claude and the call appears within seconds.
Team
Invite colleagues or your agency with a role (Admin, Analyst, Leser) – optional; mark the step as done with "Fertig".
Claude Code prompts
The install step's tab "Mit Claude Code einrichten" (and step 6) offers ready-made, project-specific prompts to paste into Claude Code inside your website repository:
| Kind | Dashboard label | MCP prompt | What Claude does |
|---|---|---|---|
install | "SDK einbauen" | /mcp__x3__install | detects stack and hosting, installs the SDK (+ proxy), connects the cookie banner, checks SPA page changes and CSP, runs the function test |
events | "Ereignisse & Ziele" | /mcp__x3__events | maps forms, CTAs, bookings, WhatsApp/phone/mail links, videos and funnels to events and goals |
server_conversions | "Serverseitige Conversions" | /mcp__x3__server_conversions | reports confirmed leads/bookings/purchases from your server with event-ID deduplication |
mcp_analysis | "Analyse mit Claude" | /mcp__x3__analysis | read-only analysis of channels, campaigns, pages and tracking quality |
troubleshoot | "Fehlersuche" | /mcp__x3__debug_tracking | finds out why no or incomplete data arrives |
connection_test | "Verbindung testen" | /mcp__x3__connection_test | three-line connection check |
Get them from the dashboard tab, as MCP prompts (rendered without project data – Claude reads it via
setup_status), with the tool setup_get_prompt or with
GET /v1/projects/{project}/setup/prompts/{kind}. Prompts never contain a secret; values that
members can edit (project, goal and domain names) appear only inside a fenced x3-daten block, and
the prompt tells Claude to treat that block as data, not instructions.
Connect Claude Code (step 2 of the Claude tab). Organization admins create a key for Claude Code ("Schlüssel für Claude Code erzeugen"); it is shown once – never put it into the repository or a chat. Recommended variant, which keeps the key out of the shell history (paste the key at the hidden prompt):
read -rs X3_KEY && claude mcp add --transport http --scope local x3 https://api.x3.example/mcp --header "Authorization: Bearer $X3_KEY"; unset X3_KEY
claude mcp get x3--scope local– only when Claude Code runs in this folder (install work, the default in the install tab);--scope user– every folder (analysis, the default in step 6). Never--scope project: it would write.mcp.jsonwith the key into the repository.- Keyless alternative:
claude mcp add --transport http --scope local x3 https://api.x3.example/mcp, then/mcp→x3→ "Authenticate" in Claude Code. On the consent page choose only this project; "Read only" is enough for analysis, installing needs "Read and write" (which can also change conversions and ad settings). - Setups registered under the old name
3xkeep working – the prompts look forsetup_statuson either server.
The Claude Code key is least-privilege: analytics:read, config:write, conversions:read,
ads:read. It deliberately cannot purge data, change destinations, roll or revoke keys, change
domains, allowed origins or the consent rules (mode, Do-Not-Track/GPC, granted/denied values), send provider test events or create proxy keys –
the prompts ask you to do those in the dashboard. Details: Claude Code.
Proxy key ownership
The first-party proxy's key (X3_PROXY_KEY) is always created by a person in the dashboard
("Proxy-Schlüssel erzeugen") – never by Claude or an OAuth app. A key minted through a Claude key or
an app sign-in is derived from it and stops working when its parent expires (Claude keys: 90 days)
or is revoked; proxied tracking would then fail with 403. The onboarding prompts therefore call
setup_generate_snippet with mint_proxy_key: false and ask you to create the key yourself.
Existing derived keys show the warning proxy_key_derived: create a new key in
the dashboard, set it at your hosting, then revoke the old one.
Test visits and conversions
Visits opened with the test parameter (?x3_test=1) are marked as test traffic. They count for
the setup checks (first pageview, hosts, custom events, goals seen) and are visible in tools with
include_test: true; reports, UTM coverage and upload counts exclude them. Conversions from test
sessions are stored with isTest and skipped by providers ("test session") unless the
destination is in test mode; web-triggered conversions from test sessions are rejected with
test_session while no destination is in test mode. Both are correct behaviour, not errors.
Which conversions need server code
The prompt "Serverseitige Conversions" sorts every conversion definition into one of three groups:
| Group | Definition | Server code |
|---|---|---|
| A – browser, automatic | trigger goal or event, no server confirmation | none – an extra server report would be a duplicate |
| B – browser, server confirms | trigger goal/event with requireServerConfirmation | confirm with conversions.create({definition, sessionId}) without an own eventId |
| C – server | trigger lead, or source api without a web trigger | leads.create / conversions.create with a stable eventId |
See Conversion definitions and the server SDK.
API
All routes are project-scoped, tagged "Setup" in the API reference, and work with a dashboard session, a secret key or an OAuth token:
| Route | Example |
|---|---|
setup.status | GET /v1/projects/acme%2Fshop/setup/status → steps, installation, goals, ads, conversions, ai, team |
setup.progress | PATCH /v1/projects/{project}/setup with {"steps": {"ads": "skipped"}} (or "open" to resume) |
setup.installation | GET /v1/projects/{project}/setup/installation?since=2026-10-15T10:00:00Z → live panel data |
setup.prompts | GET /v1/projects/{project}/setup/prompts?locale=en → the six prompt kinds |
setup.prompt | GET /v1/projects/{project}/setup/prompts/install?stack=next-app&proxy=auto&locale=de |
setup.testConversion | POST /v1/projects/{project}/setup/test-conversion with {"provider": "meta", "testEventCode": "TEST123"} |
setup.mcpActivity | GET /v1/projects/{project}/setup/mcp-activity?limit=5 → last MCP calls |
setup.detectStack | POST /v1/projects/{project}/setup/detect-stack with {"url": "https://shop.example.com"} |
setup.prompt for install, events and server_conversions needs config:write or
project:admin; the other kinds need analytics:read. Website checks and stack detection are
limited to 10 per minute and project, test conversions to 6.
The same is available over MCP: tools setup_status, setup_get_prompt,
setup_verify_installation, conversions_send_test (MCP tools) and
the resources x3://setup/checklist.md, x3://setup/prompts/{kind},
x3://projects/{project}/setup-status (MCP resources).
Reason codes
Every open item of setup.status carries one of these codes. The dashboard, the MCP tool output and
this page use the same texts.
Step group: Project
only_localhost
The project only knows localhost as a domain. Fix: Add the real domain of your website.
On screen: "Das Projekt kennt nur localhost als Domain." → "Füge die echte Domain deiner Website hinzu."
origin_missing
https://{host} is not an allowed origin – visits from there are rejected. Fix: Click “Add” to allow https://{host}.
On screen: "https://{host} ist nicht als Herkunft erlaubt – Besuche von dort werden abgelehnt." → "„Hinzufügen“ klicken, um https://{host} zu erlauben."
timezone_utc
The project's time zone is UTC. Fix: Is UTC right? Day boundaries in reports follow it.
On screen: "Die Zeitzone des Projekts ist UTC." → "Stimmt UTC? Tagesgrenzen in Berichten richten sich danach."
Step group: Install tracking
no_pageview
No pageview has arrived yet. Fix: Add the code, publish and click “Send test visit”.
On screen: "Noch kein Seitenaufruf angekommen." → "Code einbauen, veröffentlichen und „Testbesuch senden“ klicken."
stale_data
No data for more than 7 days (last {lastAt}). Fix: Check that the code is still on the website (“Check website now”).
On screen: "Seit über 7 Tagen keine Daten (zuletzt {lastAt})." → "Prüfe, ob der Code noch auf der Website ist („Website jetzt prüfen“)."
origin_rejected
Visits from {host} were rejected ({hits}×) – the origin is not allowed. Fix: If {host} belongs to you, click “Allow” under “Live verification → Domains seen”; otherwise ignore it. “Allow” adds exactly the origin the browser sent (scheme and port included).
On screen: "Besuche von {host} wurden abgelehnt ({hits}×) – die Herkunft ist nicht erlaubt." → "Gehört {host} dir, unter „Live-Prüfung → Domains gesehen“ auf „Erlauben“ klicken; sonst ignorieren."
quota_exceeded
The quota is exhausted ({reason}) – new data is dropped. Fix: Raise the quota or wait for the next period.
On screen: "Das Kontingent ist erschöpft ({reason}) – neue Daten werden verworfen." → "Kontingent erhöhen oder auf den nächsten Zeitraum warten."
ingest_paused
Data collection for this project is paused. Fix: Switch it back on in the project settings.
On screen: "Die Datenannahme für dieses Projekt ist pausiert." → "In den Projekteinstellungen wieder einschalten."
sdk_outdated
Outdated SDK version {seen} (current {latest}). Fix: Reload the script tag or update the npm package.
On screen: "Veraltete SDK-Version {seen} (aktuell {latest})." → "Script-Tag neu laden bzw. das npm-Paket aktualisieren."
verify_failed
The last website check failed (“{failed}”, the readable name of the failed check). Fix: Apply the hint of the failed check and click “Check website now” again.
On screen: "Die letzte Website-Prüfung ist fehlgeschlagen („{failed}“)." → "Den Hinweis bei der fehlgeschlagenen Prüfung umsetzen und „Website jetzt prüfen“ erneut klicken."
consent_never_granted
Mode “require”: nobody consented in 24 h – the cookie banner is probably not connected. Fix: Connect the banner (choose the CMP or call x3("consent", "granted")).
On screen: "Modus „require“: In 24 h hat niemand eingewilligt – der Cookie-Banner ist wohl nicht angebunden." → "Banner anbinden (CMP wählen oder x3("consent", "granted") aufrufen)."
proxy_ip_hidden
The proxy hides the visitor IP – countries and bot detection suffer. Fix: Set the proxy key in the proxy so the real IP is passed on.
On screen: "Der Proxy verdeckt die Besucher-IP – Länder und Bot-Erkennung leiden." → "Proxy-Schlüssel im Proxy setzen, damit die echte IP weitergegeben wird."
proxy_key_derived
Your proxy key depends on a Claude key or an app sign-in and stops working when that expires or is revoked. Fix: Create a new proxy key in the dashboard, set it at your hosting, then revoke the old one.
On screen: "Dein Proxy-Schlüssel hängt an einem Claude-Schlüssel oder einer App-Anmeldung und hört auf zu funktionieren, wenn diese abläuft oder widerrufen wird." → "Im Dashboard neuen Proxy-Schlüssel erzeugen, im Hosting eintragen, dann den alten widerrufen."
Step group: Goals
no_conversion_goal
No conversion goal yet. Fix: Choose at least one goal such as “Form submitted” or “Meeting booked”.
On screen: "Noch kein Conversion-Ziel angelegt." → "Wähle mindestens ein Ziel wie „Formular abgeschickt“ oder „Termin gebucht“."
goal_not_seen
Goal not reached in the last 24 h. Fix: Trigger the action once in a test visit.
On screen: "Ziel in den letzten 24 h nicht erreicht." → "Im Testbesuch die Aktion einmal auslösen."
event_blocked
The event {event} is blocked and dropped. Fix: Unblock it under Settings → Events.
On screen: "Das Ereignis {event} ist blockiert und wird verworfen." → "Unter Einstellungen → Ereignisse wieder freigeben."
plugin_off
The plugin “{plugin}” is off – the event is not measured. Fix: Switch the plugin on under Settings → Tracking.
On screen: "Das Plugin „{plugin}“ ist aus – das Ereignis wird nicht gemessen." → "Plugin unter Einstellungen → Tracking einschalten."
Step group: Ad platforms
composio_unavailable
Ad accounts are not set up on this installation (Composio is missing). Fix: Ask your administrator to set the Composio variables.
On screen: "Werbekonten sind auf dieser Installation nicht eingerichtet (Composio fehlt)." → "Bitte deine:n Administrator:in, die Composio-Variablen zu setzen."
platform_unavailable
The Composio configuration for {platform} is missing. Fix: Create the platform's auth config in Composio.
On screen: "Für {platform} fehlt die Composio-Konfiguration." → "Auth-Konfiguration für die Plattform in Composio anlegen."
connection_initiated
Sign-in at {platform} not completed. Fix: Click “Reconnect”.
On screen: "Anmeldung bei {platform} nicht abgeschlossen." → "„Erneut verbinden“ klicken."
connection_expired
The connection to {platform} has expired. Fix: Click “Reconnect”.
On screen: "Die Verbindung zu {platform} ist abgelaufen." → "„Neu verbinden“ klicken."
connection_failed
The connection to {platform} failed. Fix: Click “Reconnect” and complete the sign-in.
On screen: "Die Verbindung zu {platform} ist fehlgeschlagen." → "„Neu verbinden“ klicken und die Anmeldung vollständig abschließen."
no_accounts_selected
Connected, but no ad account selected yet. Fix: Select accounts – the sync starts afterwards.
On screen: "Verbunden, aber noch kein Werbekonto ausgewählt." → "Konten auswählen – danach startet die Synchronisierung."
sync_running
The sync is running. Fix: Wait a few minutes.
On screen: "Die Synchronisierung läuft." → "Ein paar Minuten warten."
sync_error
The last sync failed ({errorKind}). Fix: Reconnect or start the sync again.
On screen: "Die letzte Synchronisierung ist fehlgeschlagen ({errorKind})." → "Neu verbinden oder die Synchronisierung erneut starten."
no_spend
Synced, but no spend in 30 days. Fix: Normal for paused accounts – otherwise select the right account.
On screen: "Synchronisiert, aber keine Ausgaben in 30 Tagen." → "Für pausierte Konten normal – sonst das richtige Konto auswählen."
utm_partial
Only {coverage} % of ad clicks carry UTM parameters. Organic Facebook links also carry fbclid; small gaps are normal. Fix: Add the UTM template to every campaign.
On screen: "Nur {coverage} % der Anzeigenklicks haben UTM-Parameter. Organische Facebook-Links tragen auch fbclid; kleine Lücken sind normal." → "UTM-Vorlage in allen Kampagnen eintragen."
utm_missing
Most ad clicks carry no UTM parameters ({coverage} %). Fix: Copy the UTM template and enter it in the platform.
On screen: "Die meisten Anzeigenklicks haben keine UTM-Parameter ({coverage} %)." → "UTM-Vorlage kopieren und in der Plattform eintragen."
no_paid_traffic
No ad clicks in the last 14 days. Fix: Coverage appears here once ads are running.
On screen: "Keine Anzeigenklicks in den letzten 14 Tagen." → "Sobald Anzeigen laufen, erscheint die Abdeckung hier."
unmatched_utms
{count} UTM combinations match no campaign. Fix: Review them under Ad costs → Unmatched.
On screen: "{count} UTM-Kombinationen passen zu keiner Kampagne." → "Unter Werbekosten → Nicht zugeordnet prüfen."
Step group: Conversions
no_definitions
No conversion definition yet – nothing is reported. Fix: Apply a suggestion from your goals.
On screen: "Noch keine Conversion-Definition – es wird nichts gemeldet." → "Vorschlag aus deinen Zielen übernehmen."
destination_disabled
{provider} is switched off. Fix: Switch the destination on.
On screen: "{provider} ist ausgeschaltet." → "Ziel einschalten."
secrets_missing
Credentials missing: {names}. Fix: Enter the credentials or “Use the connection from step 4”.
On screen: "Zugangsdaten fehlen: {names}." → "Zugangsdaten eintragen oder „Verbindung aus Schritt 4 verwenden“."
settings_invalid
Invalid setting: {path}. Fix: Correct the field and save.
On screen: "Einstellung ungültig: {path}." → "Feld korrigieren und speichern."
mode_dry_run
Dry run – nothing is sent to the platform. Fix: Switch to test mode or live after testing.
On screen: "Trockenlauf – es wird nichts an die Plattform gesendet." → "Nach dem Test auf Testmodus oder Live stellen."
mode_test
Test mode – does not count at the platform. Fix: After a successful test, go live now.
On screen: "Testmodus – zählt nicht bei der Plattform." → "Nach erfolgreichem Test jetzt live schalten."
test_missing
Not tested yet. Fix: Click “Send test event”.
On screen: "Noch nicht getestet." → "„Test-Event senden“ klicken."
test_failed
The last test failed ({status}). Fix: Read the platform's answer, check the credentials and test again.
On screen: "Der letzte Test ist fehlgeschlagen ({status})." → "Antwort der Plattform lesen, Zugangsdaten prüfen und erneut testen."
upload_errors
{count} uploads failed in 30 days, none succeeded. Fix: Look at the error under Conversions and check the credentials.
On screen: "{count} Uploads in 30 Tagen fehlgeschlagen, keiner erfolgreich." → "Unter Conversions die Fehlermeldung ansehen und Zugangsdaten prüfen."
Step group: Claude and permissions
no_mcp_calls
Claude is set up but has not called X3 yet. Fix: Paste the prompt “Test the connection” into Claude.
On screen: "Claude ist eingerichtet, hat X3 aber noch nicht aufgerufen." → "Prompt „Verbindung testen“ in Claude einfügen."
mcp_errors
Every Claude call of the last 30 days failed. Fix: Read the error in the function test; renew the key or sign-in.
On screen: "Alle Claude-Aufrufe der letzten 30 Tage sind fehlgeschlagen." → "Fehlertext im Funktionstest lesen; Schlüssel oder Anmeldung erneuern."
no_permission
You lack the permission for this. Fix: Ask an admin to complete this step.
On screen: "Dafür fehlt dir die Berechtigung." → "Bitte eine:n Admin, diesen Schritt zu erledigen."