Skip to main content
Flows represent an end-to-end user journey in Corbado Observe, for example login or sign-up. The flow concept powers dashboards that show the different paths of a given flow. For example, the Login Funnel dashboard can render an overview of all variants that exist for a login journey.
Each flow is built from smaller units:
  • Decisions, the checkpoints where a user or your system picks a method or a route
  • Subflows, one concrete auth method attempt each
  • Nested flows, a complete journey inside another one, such as a recovery started from the login page
Flows are not auto-discovered. You send the flow events yourself, and every event you send attributes to the innermost open flow. Read Modeling first if you have not defined your flow boundaries yet.

1. Flow names

Use one of these predefined flow names when you send flow events:
Flow nameOutcome statesDescription
logincomplete or incompleteExisting user authentication. Typically contains the pre-identifier and post-identifier decisions, subflows such as provide-identifier, passkey-login, password-login, social-login, email-otp or email-link, and possibly a nested signup or recovery.
signupcomplete or incompleteNew user registration. Similar subflows to login, usually with the enrollment spec types, and possibly a nested recovery.
recoverycomplete or incompleteRegaining access to an account, for example a password reset. Usually nested inside a login flow.
enrollmentcomplete, skipped, visible-auto-skip, invisible or incompleteAuthenticator enrollment, for example a passkey setup prompted after login. Typically contains an enrollment-user decision and a passkey-enrollment subflow.
You can also use custom flow names for authentication-related journeys that do not fit the predefined ones, such as account renewal, re-authentication or transaction signing. Custom flow names can be used in custom dashboards. The predefined dashboards do not pick them up automatically.

2. Flow events

2.1 flowStarted()

Emit flowStarted() at the flow’s own entry screens: the login page, the sign-up page, the “forgot password” screen, the enrollment prompt. Use flowName when the flow is known from the start. Use flowNames when several flow names are possible at entry. This is common for identifier-first journeys where login and sign-up share one form: list both, optionally with defaultFlowName to indicate which one Corbado Observe should assume if the journey is never resolved. Set touchpoint to describe where the flow was initiated. The same flow started from checkout behaves very differently from the same flow started from the account page, and the funnels are segmented by it. If the user is already known when the flow starts, which is normal for enrollment, include userId in flowStarted().
Fire flowStarted() from one declared place per flow, never from a nested page “to make sure the parent is open”. A repeated start of the innermost open flow is merged, but a repeated start of an outer flow while a nested flow is open is read as a restart: the nested flow closes as incomplete. Frameworks that mount route components twice per navigation make this an easy mistake.
flowStarted(data, tags?, experiments?, contexts?) accepts structured context as its fourth argument. Context is written to the event’s contexts field without redaction; use it for non-sensitive technical metadata, and use tags for analytical dimensions. Omit unused arguments with undefined.

2.2 flowDecided()

Emit flowDecided() when a journey that started with flowNames resolves to one concrete flow, typically after your backend has checked whether the identifier belongs to an existing user. If it is emitted more than once, the last flow name wins. A flow name provided by a later flowFinished() also wins.

2.3 flowFinished()

Emit flowFinished() when the flow reaches its explicit end state. For login, that is the moment the user is authenticated. For sign-up, the moment the account exists and the user is authenticated. For recovery, the moment access is regained. For enrollment, the moment the authenticator is set up. Include userId and identifier so the completed flow links to a user. Do not model non-completion. A flow the user abandons is classified as incomplete from the absence of a flowFinished(). The one exception is an explicit skip, which is semantically different from abandoning: use explicitOutcome. A skip carries the user reference whenever identity is already known.

2.4 flowAutoFinished()

Only login and signup can contain nested flows. A flow that itself establishes the session, such as a sign-up or a recovery started from the login page, nests inside login. When the nested flow’s terminal fires, finish it with flowFinished() and complete the parent with flowAutoFinished():
A flowAutoFinished() without an open parent is dropped silently. A flow that runs after the session already exists, typically an enrollment prompted after login, is not nested. It is a sibling flow started after the login finished, with its own flowStarted() and flowFinished(). When the user leaves a nested flow without finishing it, close it with flowFinished({ flowName, explicitOutcome: "skipped" }). That records the right outcome and keeps the parent’s subsequent events out of the nested flow.

2.5 flowReset()

flowReset() exists for an explicit restart by the user, for example navigating back to the start of a login. It is rarely needed: whether a user restarted is inferable later from revisited decisions and subflows. Do not emit it just to be tidy.

3. Ordering

Ordering requirements are causal, not temporal:
  1. flowStarted() before any event of that flow. A flowFinished() or flowDecided() without an open flow invalidates the whole session’s classification.
  2. When a screen renders, the decision started comes before the operation helpers are created.
  3. Settle the previous screen before opening the next: a navigational decision finished precedes the next screen’s decision started.
Nothing else. The backend orders by timestamp and emission sequence and repairs known race patterns. Use explicitTimestamp on steps and decisions to back-date an event when the semantic moment precedes the tracking call. A flow carried across page loads also needs the same Observe session. The SDK checks inactivity at initialization, so an expired session can split a cross-load flow. Ensure the project’s inactivity window spans the journey and initialize the same project on each page. See session lifetime.

4. Tags on flows

Flows are the natural carrier for tags. Configuration known at entry, such as product, variant or device class, goes on flowStarted(). Values only known on success go on flowFinished(). The last value per key wins across a flow’s events. Never put identity into tags.

5. Conversions

conversion() records a business conversion outside authentication, such as a completed purchase, so that authentication outcomes can be related to what happened next:

6. Examples

In a real application, flow tracking is spread across pages, components, server actions and callback routes. The examples keep the relevant snippets together to show the complete sequence for each journey.

6.1 Recovery with automatic login

A login flow is started, the user cannot log in and switches to recovery. After completing recovery through an email link and setting a new password, the user is logged in automatically. Recovery nests inside the surrounding login. When recovery completes, finish recovery explicitly and complete login with flowAutoFinished(). Because the email link can be opened in a different browser or device, pass a stable crossEnvironmentTransactionID from the send step to the verification step.

6.2 Recovery without automatic login

Same journey, but the user is not logged in after the reset and has to log in again with the new password. Recovery is still nested inside login. The difference is that login stays open after the nested recovery finished and contains a further password-login subflow before it finishes explicitly.
More journeys, including sign-up with automatic login and combined forms, are in the journey catalog.

7. Next steps

  • Continue with Decisions to model the checkpoints inside flows.
  • Continue with Subflows to learn about subflow types and their steps.