Skip to main content
Flows represent an end-to-end user journey in Corbado Observe, for example login or sign-up. Each flow is built from smaller units:
  • Decisions (for example method selection or route choice)
  • Subflows (for example passkey login, password login, or email OTP)
In contrast to subflows, flows are not auto-discovered by Corbado Observe. You need to manually define flows by sending the appropriate flow events which are listed below.

1. Supported flow types

Use one of these flow names when you send flow events:
Flow typeMeaningTypical outcome
loginExisting user authentication journeyUser is authenticated
signupNew user registration journeyUser account is created and authenticated
recoveryAccount recovery journeyUser regains access and is authenticated
enrollmentAuthenticator enrollment journey (for example passkey setup)Additional auth method is configured

2. Flow lifecycle events

These Corbado Observe SDK events define and update flow state:
Event methodWhen to sendRequired fields
flowStarted()User enters an auth touchpoint or flow chooserflowName or flowNames
flowDecided()Flow is resolved to one concrete pathflowName
flowFinished()Flow completes explicitlyflowName
flowReset()In-progress flow is restartedflowName
flowAutoFinished()One flow is completed implicitly by another flowflowName, finishedByFlowName
In most implementations, you always send flowStarted() and flowFinished(). Send flowDecided() when you start with multiple possible flows and resolve later.

3. flowName vs flowNames

Use the flow fields based on how many outcomes are possible at the start:
  • flowName: Use when the path is already known (for example a dedicated /login page).
  • flowNames: Use when multiple paths are possible at entry time (for example combined identifier-first form that can lead to login or sign-up).
  • defaultFlowName: Optional fallback used with flowNames when you need an initial default (for example login).

3.1 Example: Split login and sign-up routes

Use this pattern when login and sign-up are hard-separated by route or UI entry point.
Corbado Observe SDK installation and setup are explained in Getting started.
import { init, getTracker } from "@corbado/observe";

init({
  projectId: "<ProjectID>",
  apiBaseUrl: "<APIBaseURL>"
});

// /login route
getTracker().flowStarted({
  flowName: "login",
  touchpoint: "account_login",
});

// ... authentication succeeds
getTracker().flowFinished({
  flowName: "login",
  userId: "usr_123",
});

// /signup route
getTracker().flowStarted({
  flowName: "signup",
  touchpoint: "account_signup",
});

// ... registration succeeds
getTracker().flowFinished({
  flowName: "signup",
  userId: "usr_456",
});

3.2 Example: One combined auth entry point

Use this pattern when one form can result in either login or sign-up. At entry time, you do not know the final flow yet, so send flowNames and optional defaultFlowName. Later, after identifier lookup or backend checks, decide and finish the resolved flow.
Corbado Observe SDK installation and setup are explained in Getting started.
import { init, getTracker } from "@corbado/observe";

init({
  projectId: "<ProjectID>",
  apiBaseUrl: "<APIBaseURL>",
});

// Combined /auth route
getTracker().flowStarted({
  flowNames: ["login", "signup"],
  defaultFlowName: "login",
  touchpoint: "account",
});

// Later after user exists check:
var userExists = true;

if (userExists) {
  getTracker().flowDecided({ flowName: "login" });
  getTracker().flowFinished({
    flowName: "login",
    userId: "usr_123",
  });
} else {
  getTracker().flowDecided({ flowName: "signup" });
  getTracker().flowFinished({
    flowName: "signup",
    userId: "usr_789",
  });
}

4. Next steps

  • Continue with Decisions to model branch points inside flows.
  • Continue with Subflows to map concrete auth steps.