Anwendungsfälle

Deinen kompletten SaaS-Funnel tracken (Sign-up → Trial → Kauf)

Die komplette Anleitung für SaaS-Produkte – ordne jede Stufe deines Lifecycles dem Werbe-Klick, Post oder der E-Mail zu, die ihn gebracht hat. Tracke Demo-Calls und Free-Sign-ups, dann Trial gestartet, Trial beendet und den bezahlten Kauf. Käufe kommen automatisch über Stripe rein; die In-Product-Meilensteine sendest du mit einer winzigen Server-Route. Die Kernidee: die Click-ID einmal beim Sign-up erfassen und bei jedem späteren Event erneut mitschicken.

Bei einem SaaS-Produkt verteilen sich die spannenden Momente über Tage oder Wochen und fast alle passieren in deiner App, nicht auf einer Marketing-Seite: jemand bucht eine Demo, erstellt ein kostenloses Konto, startet einen Trial, der Trial endet und – irgendwann – zahlt er. Diese Anleitung verbindet jede dieser Stufen mit LeadJourney, damit du pro Kampagne, Anzeigengruppe und Anzeige nicht nur siehst, wie viele Sign-ups eine Anzeige gebracht hat, sondern wie viele davon zu Trials und zahlenden Kunden wurden.

Tracking muss zuerst live sein

Jede Stufe wird dem Besucher zugeordnet, der auf deine Anzeige geklickt hat – das Tracking-Script muss also zuerst auf deiner Seite laufen. Falls nicht, beginne mit Tracking-Script einbinden (und den Entwickler-Hinweisen unter Auf einer custom-coded Website einbinden).

Der SaaS-Funnel, gemappt auf LeadJourney

Wähle die Stufen, die es in deinem Produkt gibt, und lass den Rest weg. Jeder Event-Name ist nur der type, den du sendest – benenne sie um in das, was dein Team ohnehin sagt.

Funnel-StufeEvent-typeWie du es sendestUmsatz?
Demo-Call gebucht (sales-assisted)BookedCallWebhook deines Schedulers (Calendly, …)
Free-Sign-up / Konto erstelltFreeSignupEine winzige Server-Route (Conversion API)
Trial gestartetTrialStartedDieselbe Server-Route
Trial beendetTrialEndedDieselbe Server-Route (aus deiner Billing-Logik / einem Cron)
Kauf (Trial wird bezahlt)WonClientStripe-Integration – automatisch

Zwei Wege, eine Stufe zu senden – und wann welcher

  • Kauf → nutze die Stripe-Integration. Echte Geldereignisse tragen den Umsatz und ordnen sich selbst zu, ohne Code. Das ist der einfache Teil.
  • Demo-Call → nutze den Webhook deines Schedulers. Die meisten Buchungstools (Calendly, SavvyCal, …) können bei einer Buchung eine URL anpingen.
  • Free-Sign-up, Trial gestartet, Trial beendet → sendest du selbst mit einer kleinen Server-Route. Das sind In-Product-Zustandswechsel, die nur deine App kennt – also definierst du den Trigger und postest das Event an LeadJourneys Conversion API.

Die eine Idee, die SaaS-Tracking funktionieren lässt

Hier ist der Haken, der für SaaS einzigartig ist – und das Wichtigste in dieser Anleitung:

Die Click-ID liegt beim Sign-up im Browser. Aber Trial gestartet, Trial beendet und Kauf passieren später – oft serverseitig, in einem Webhook oder einem nächtlichen Cron, ohne Browser und ohne localStorage.

Du kannst die Click-ID also nicht erneut auslesen, wenn diese späteren Events feuern. Die Lösung ist einfach:

Die Click-ID einmal erfassen, am User speichern, für immer wiederverwenden

Beim Sign-up liest du localStorage.getItem('clickId') und speicherst sie am User-/ Konto-Datensatz in deiner eigenen Datenbank (eine Spalte wie lj_click_id). Jedes spätere Lifecycle-Event – Trial gestartet, Trial beendet, sogar ein manuelles Upgrade – liest diesen Wert zurück und schickt ihn mit. Einmal erfassen, für die ganze Customer Journey wiederverwenden.

Die E-Mail ist dein Sicherheitsnetz

LeadJourney matcht Events auch über die E-Mail. Sende die E-Mail des Users immer mit jedem Event, damit die Stufen – selbst wenn nie eine Click-ID erfasst wurde (organischer oder direkter Sign-up) oder sie verloren ging – auf dieselbe Person zusammengeführt werden und später zugeordnet werden können, falls die Person vorher auf einen getrackten Link geklickt hat. Siehe Wie Attribution funktioniert.

Bevor du startest

Hol dir deine Conversion-API-Zugangsdaten unter Settings → Events → Conversion API:

  • Deine Postback-URL – sieht aus wie https://api.leadjourney.io/api/v1/postback/<deine-id>.
  • Dein Postback-Secret – beginnt mit pb_…. Behandle ihn wie ein Passwort; er bleibt auf deinem Server und wird nie im Browser sichtbar.

Lege dann im Events Manager die Events an, die du nutzen willst. WonClient gibt es in jedem Workspace standardmäßig; füge die In-Product-Events hinzu:

EventEmpfohlener ModusHinweise
FreeSignupIgnore duplicate postbacksEins pro User – verwirft versehentliche Wiederholungen.
TrialStartedIgnore duplicate postbacksEin User startet einen Trial einmal.
TrialEndedIgnore duplicate postbacksSchalte Send back to Ads aus – es ist ein Drop-off-Signal, keine Conversion.
WonClientEdit conversion with new dataSchalte Include Revenue Value ein.

Warum „Ignore duplicate postbacks“ hier wichtig ist

Lifecycle-Events werden oft aus erneut zugestellten Webhooks oder einem Cron, der User neu durchgeht gesendet – dasselbe Event kann also mehrfach für dieselbe Person feuern. Diese auf Ignore duplicate postbacks zu setzen hält deine Funnel-Zahlen ehrlich. Siehe die Modus-Tabelle in Events & der Events Manager.

Schritt für Schritt

Die Click-ID beim Sign-up erfassen und speichern

Wenn ein neues Konto erstellt wird, liest du die Click-ID aus dem Browser und speicherst sie am User. Mach das einmal – davon hängt jede spätere Stufe ab.

// In deinem Sign-up-Handler, im Moment, in dem das Konto erstellt wird
const clickId = localStorage.getItem("clickId") || "";

// Am User-Datensatz speichern (Pseudo-Code – nutze dein eigenes ORM / deine DB)
await db.users.update(newUser.id, { lj_click_id: clickId });

SSO-Sign-ups (Google / Microsoft)

Bei Google- oder Microsoft-Registrierung wird das Konto in einem Callback erstellt, wo localStorage eventuell weg ist. Reiche die Click-ID über den OAuth-state-Parameter durch und speichere sie genauso – das komplette Muster steht in Sign-ups in deiner App tracken (inkl. Google & Microsoft SSO).

Eine Server-Route hinzufügen, die jedes Lifecycle-Event sendet

Diese eine Route leitet ein Event an LeadJourney weiter und hält deinen Postback-Secret sicher auf dem Server. Das Beispiel ist eine Next.js-App-Router-Route; jedes Backend (Express, eine Serverless-Funktion) funktioniert genauso.

// app/api/lj-event/route.js
export async function POST(req) {
  const d = await req.json();

  await fetch(
    "https://api.leadjourney.io/api/v1/postback/YOUR-POSTBACK-ID",
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer YOUR_POSTBACK_SECRET",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        type: d.type,              // "FreeSignup" | "TrialStarted" | "TrialEnded" | "WonClient"
        click_id: d.click_id,      // der Wert, den du beim Sign-up gespeichert hast
        email: d.email,            // immer mitschicken – der dauerhafte Match-Schlüssel
        firstname: d.firstname,
        lastname: d.lastname,
        tag: d.tag,                // optionaler Quellen-/Kampagnen-Tag, ein festes Wort genügt
        revenue: d.revenue,        // nur für WonClient – sonst weglassen
      }),
    }
  );

  return Response.json({ ok: true });
}

Nutze deine eigene URL und deinen eigenen Secret

Ersetze YOUR-POSTBACK-ID und YOUR_POSTBACK_SECRET durch die Postback-URL und den Postback-Secret aus deinen eigenen Settings → Events → Conversion API. Schreibe den Secret nie in Client-Code und committe ihn nie in ein öffentliches Repo.

Jede In-Product-Stufe feuern

Bei jedem Lifecycle-Wechsel rufst du die Route mit dem passenden type auf. Weil du die Click-ID beim Sign-up gespeichert hast, kann jeder Aufruf sie mitschicken – ganz ohne Browser.

Feuere einmal, direkt nach der Konto-Erstellung (an derselben Stelle, wo du die Click-ID gespeichert hast). Wenn du die Click-ID hier direkt aus localStorage liest, brauchst du sie noch nicht mal aus der DB.

await fetch("/api/lj-event", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    type: "FreeSignup",
    click_id: localStorage.getItem("clickId"), // oder der gerade gespeicherte Wert
    email: newUser.email,
    firstname: newUser.firstName,
    lastname: newUser.lastName,
    tag: "saas_signup",
  }),
});

Feuere, wenn der Trial tatsächlich beginnt – das kann beim Sign-up sein (Self-Serve-Trial) oder später (der User hinterlegt eine Karte, wählt einen Plan oder aktiviert einen Trial). Lies die gespeicherte Click-ID:

await sendEvent({
  type: "TrialStarted",
  click_id: user.lj_click_id,   // der Wert, den du beim Sign-up gespeichert hast
  email: user.email,
  firstname: user.firstName,
  lastname: user.lastName,
  tag: "saas_trial",
});

Sign-up und Trial im selben Moment?

Bei einem klassischen Product-Led-Trial gilt Free-Sign-up = Trial gestartet. Du kannst entweder beide Events senden (sauberer Funnel) oder nur eins – sende die Stufe, über die dein Team tatsächlich berichtet.

Hinter dieser Stufe steckt meist keine User-Aktion – es ist ein Datum, das vergeht. Feuere sie aus deiner Billing-Logik, wenn du den Trial auf abgelaufen setzt, oder aus einem nächtlichen Cron, der Trials findet, deren Enddatum in der Vergangenheit liegt:

// Pseudo-Code für einen täglichen Job
const expired = await db.trials.findExpiredSince(yesterday);
for (const t of expired) {
  await sendEvent({
    type: "TrialEnded",
    click_id: t.user.lj_click_id,
    email: t.user.email,
    tag: "saas_trial_end",
  });
}

Trial beendet ist ein Signal, kein Verkauf

Trial beendet ist vor allem nützlich, um zu sehen, wo der Funnel leckt – welche Kampagnen Trials bringen, die nie konvertieren. Lass Send back to Ads für dieses Event aus, damit du nicht darauf optimierst.

Für diese Stufe brauchst du keinen Code – überlass das der Stripe- Integration. Mappe ein Stripe-Event Zahlung erfolgreich (oder Subscription erstellt) auf deine WonClient-Conversion, und der Umsatz wird automatisch zugeordnet. Das Tracking-Script versieht deine Stripe Payment Links sogar mit der Click-ID, sodass Zahlungen der richtigen Kampagne zugeordnet werden – ohne dass du etwas von Hand mappst.

Wenn du außerhalb von Stripe abrechnest, sende es stattdessen über dieselbe Route – mit revenue:

await sendEvent({
  type: "WonClient",
  click_id: user.lj_click_id,
  email: user.email,
  revenue: 49,          // der gezahlte Betrag, in deiner Workspace-Währung
  tag: "saas_purchase",
});

Demo-Calls (sales-assisted SaaS)

Wenn Interessenten vor dem Sign-up eine Demo buchen, tracke das als BookedCall. Die meisten Scheduler (Calendly, SavvyCal, …) können bei einer Buchung einen Webhook posten – richte ihn auf eine kleine Route wie oben und sende type: "BookedCall" mit der E-Mail des Buchenden. Um den Call der richtigen Anzeige zuzuordnen, reiche die Click-ID in den Buchungslink durch (als verstecktes Feld / UTM, das der Scheduler zurückgibt) oder verlass dich auf E-Mail-Matching. Siehe den Sales Call Funnel und Events & der Events Manager für das komplette Muster.

Wiederkehrender Umsatz (MRR) ohne Doppelzählung

Ein Abo zahlt jeden Monat – entscheide einmal, wie das auftauchen soll:

  • Eine Conversion, aktuell gehalten – setze WonClient auf Edit conversion with new data, sodass die letzte Zahlung dieselbe Conversion aktualisiert (gut für „aktueller MRR / Planwert").
  • Jede Zahlung als neuer Umsatz – setze es auf Keep original data, increment payout, sodass jede Rechnung obendrauf addiert wird (gut für „insgesamt eingenommen"). Dieser Modus gilt nur für WonClient.
  • Refunds und Churn herausrechnen – mappe einen Stripe-Refund auf eine Conversion mit negativem revenue, oder sende ein Churned-Event, um zu sehen, welche Kampagnen Kunden bringen, die kündigen.

Siehe die Modus-Tabelle in Events & der Events Manager.

Prüfen, ob es funktioniert

  1. Öffne deine App über einen getrackten Link (einen mit lj_*-Parametern, z. B. eine Test- Anzeigen-URL oder ein mit dem UTM Builder gebauter Link), damit eine Click-ID gespeichert wird.
  2. Geh den Funnel mit einem Testkonto durch: registrieren, Trial starten, Trial beenden auslösen (oder auf den Cron warten) und einen Test-Kauf in Stripe machen.
  3. Unter Settings → Events steigt der Total-Zähler jedes Events und sein Status wechselt auf Active.
  4. Um einen einzelnen Aufruf zu prüfen (Statuscode, Payload, ob die Click-ID oder E-Mail gematcht hat), öffne das API-Postbacks-Log.

Keine Click-ID? Trotzdem das Event senden

Ein User, der organisch oder direkt kam, hat keine Click-ID – click_id ist dann leer, und das ist okay. Sende jede Stufe trotzdem mit der E-Mail: LeadJourney matcht auch über die E-Mail, das Konto wird also erfasst und kann später zugeordnet werden, falls die Person vorher auf einen getrackten Link geklickt hat. Blockiere ein Lifecycle-Event nie nur, weil die Click-ID fehlt.

FAQ

Verwandte Artikel

Auf dieser Seite