> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corbado.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Decisions with Corbado Observe

> Track the checkpoints of an authentication journey with decisions in Corbado Observe.

Decisions capture the checkpoints of an authentication journey: the screens where a user or your system picks an authentication method or a route.

<Frame>
  <iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/CLogBNykxnQ" title="Passkey vs Password: See what Users actually use | Corbado Observe" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />
</Frame>

Unlike [subflows](/corbado-observe/tracking/subflows), decisions are **not auto-discovered**. You define them by sending decision events from your integration.

## 1. Why decisions matter

Without decision events, branch points are only implied by downstream events and funnels are hard to interpret. With them you can:

* See which methods were offered on a screen versus which one was taken
* Understand where users hesitate, go back or switch methods repeatedly
* Analyze system-driven routing, for example what the post-identifier screen offered
* Filter and segment funnels by concrete choice outcomes

## 2. Decision model

A decision in **Corbado Observe** has:

* A stable `decisionName`
* The list of `options` offered at the checkpoint
* A chosen value (`explicitDecisionValue`) once the decision resolves

### 2.1 Decision names are checkpoints

A decision name is a semantic unit of the journey, usually a checkpoint that takes a successful auth method to pass and navigation to leave. Several screens map to one name: progressive disclosure, explanatory screens or switching between verification methods are still the same checkpoint. Typical names:

| Name | Covers |
| - | - |
| `pre-identifier` | Everything before the identifier is submitted: the identifier field, social buttons, a discoverable passkey button. |
| `post-identifier` | The methods shown after the identifier is known: password, passkey, OTP, "other methods". |
| `2fa` | A second-factor checkpoint after a first method succeeded. |
| `enrollment-user` | A post-login prompt offering to enroll a passkey, with a skip. |

Rules that make names aggregate well:

* Every screen belongs to a decision, even with a single option.
* The same semantic screen always maps to the same name. A screen reused across checkpoints maps per surrounding context.
* Keep names short, stable and independent of UI wording or experiments.

### 2.2 Two kinds of options, one option set

A screen's option set usually mixes both:

| Kind | Examples | Lifecycle |
| - | - | - |
| **Method options** | password, passkey, OTP, social login | Predefined option strings (see section 4). **Never finished explicitly.** The subflow that follows resolves the decision in classification, and a failed attempt resolves it just as well as a successful one. |
| **Navigational options** | back, change identifier, switch to sign-up, other methods, forgot password, skip | Free-form names. Finish explicitly with `explicitDecisionValue` the moment the choice is made, typically followed by the next screen's decision `started`. |

Realistic logins have many navigational options. They are the adaptive, per-application part of the model, not an edge case. Name them for reuse across decisions, for example `back`, `switch-to-signup` or `recovery`.

A screen can lead into the same subflow through several controls, for example two buttons that run the same ceremony or a primary tile plus an "other methods" list. That is one option and one subflow, because only one option string can resolve it. Express the difference through a spec type where the taxonomy has one, otherwise carry it as a tag.

## 3. Decision events

Use `authMethodsDecisionStarted()` and `authMethodsDecisionFinished()` for every checkpoint in an authentication journey. The `options` array takes the predefined method strings and any free-form navigational strings side by side.

| Event | SDK method | Send when | Fields |
| - | - | - | - |
| `auth_method_decision_started` | `authMethodsDecisionStarted()` | The checkpoint is presented, or its options change | `decisionName`, `options`, optional `explicitTimestamp` |
| `auth_method_decision_finished` | `authMethodsDecisionFinished()` | A navigational choice is made | `decisionName`, `explicitDecisionValue` |

```typescript theme={null}
import { getTracker } from "@corbado/observe";

// The post-identifier screen renders.
getTracker().authMethodsDecisionStarted({
  decisionName: "post-identifier",
  options: ["password-login-known-identifier", "passkey-login-known-identifier", "switch-to-otp", "back"],
});

// The user picks a method: no finished event. The password or passkey subflow resolves it.

// The user picks navigation: finish explicitly.
getTracker().authMethodsDecisionFinished({
  decisionName: "post-identifier",
  explicitDecisionValue: "switch-to-otp",
});
```

<Note>
  The chosen value must be one of the declared options, otherwise the decision is dropped. If you pass `options` on `finished`, pass exactly the array you declared on `started`. A different set is classified as a second occurrence.
</Note>

`authDecisionStarted()` and `authDecisionFinished()` are deprecated. Use `authMethodsDecisionStarted()` and `authMethodsDecisionFinished()` for every decision, including prompts that offer no auth method: their `options` accept free-form strings.

### 3.1 A decision occurrence is a presentation

Every presentation of a checkpoint to the user is its own decision occurrence. Re-send `started` whenever the checkpoint is re-presented or its options change. The superseded occurrence closes as incomplete, which is exactly what a revisited checkpoint should look like.

Do not re-send `started` for:

* **Framework re-renders, route remounts or hydration.** An identical offer with no user action in between is the same presentation.
* **A failed attempt on an unchanged screen.** Engaging a method resolves the decision regardless of how the attempt ends. A retry is a retry inside the same occurrence.

A click-driven decision, on the other hand, is always a new presentation.

### 3.2 Options that depend on an async capability check

When the option set depends on an asynchronous check, such as conditional mediation support or platform authenticator availability, emit `started` synchronously on render with `explicitTimestamp` set to the render time. Once the check resolves, re-emit with the final options and the **same** timestamp. An identical timestamp replaces the open occurrence's options in place instead of opening a new one.

```typescript theme={null}
const presentedAt = Date.now();

getTracker().authMethodsDecisionStarted(
  { decisionName: "pre-identifier", options: ["identifier-email", "social-google"] },
  {},
  { explicitTimestamp: presentedAt },
);

const cuiAvailable = await PublicKeyCredential.isConditionalMediationAvailable?.();
if (cuiAvailable) {
  getTracker().authMethodsDecisionStarted(
    { decisionName: "pre-identifier", options: ["identifier-email", "passkey-login-cui", "social-google"] },
    {},
    { explicitTimestamp: presentedAt },
  );
}
```

An auth-method option whose subflow auto-starts on the screen still belongs in the option set. Silent [trusted-device checks](/corbado-observe/tracking/subflows#3-12-trusted-device-check-and-enrollment) are system work, not offered methods: they have no decision option and never resolve a decision.

## 4. Option strings that subflows resolve

A method option only resolves if the exact string is present in `options`:

| Subflow (spec type) | Option string |
| - | - |
| password-login (`password-known-identifier`) | `password-login-known-identifier` |
| password-login (`password-with-identifier`) | `password-login-with-identifier` |
| passkey-login (`passkey-known-identifier`, `passkey-known-identifier-auto`, `passkey-cui`, no spec) | `passkey-login-known-identifier` |
| passkey-login (`passkey-no-identifier`, `passkey-no-identifier-auto`) | `passkey-login-no-identifier` |
| passkey-login (`passkey-immediate`) | `passkey-login-immediate` when the ceremony finishes or a failed prompt counts as interaction; a silent rejection does not resolve it. |
| password-login with its `cui` steps fired | `passkey-login-cui` |
| passkey-enrollment | `passkey-enrollment` |
| password-enrollment | `password-set` or `password-reset`, fallback `password-enrollment` |
| email-otp | `email-otp-login` or `email-otp-enrollment`, fallback `email-otp` |
| sms-otp | `sms-otp-login` or `sms-otp-enrollment`, fallback `sms-otp` |
| email-link | `email-link-login` or `email-link-enrollment`, fallback `email-link` |
| provide-identifier | `identifier-email`, also when its Conditional UI part completes. A CUI ceremony torn down by the identifier submit is neutral. |
| provide-data | `provide-data` |
| social-login | `social-<provider>` for the reported provider; fallback `social-other` for providers other than Google, Apple and Facebook, or when the provider is missing |
| sso-login | `sso-login` |
| app-confirmation | `qr-code` on a dedicated QR screen, otherwise `app-confirmation` |
| totp | `totp-login` or `totp-enrollment`, fallback `totp` |

"Fallback" means the subflow first tries the spec-typed string, then the generic one. Put whichever your option set naturally distinguishes.

## 5. Examples

### 5.1 Post-login passkey enrollment prompt

A binary checkpoint: enroll or skip. Enrolling leads into a subflow, skipping is navigational.

```typescript theme={null}
// The prompt is shown.
getTracker().authMethodsDecisionStarted({
  decisionName: "enrollment-user",
  options: ["passkey-enrollment", "skip"],
});

// Case A: the user clicks "Create passkey". No finished event; the
// passkey-enrollment subflow resolves the decision.

// Case B: the user clicks "Skip".
getTracker().authMethodsDecisionFinished({
  decisionName: "enrollment-user",
  explicitDecisionValue: "skip",
});
getTracker().flowFinished({ flowName: "enrollment", explicitOutcome: "skipped", userId });
```

### 5.2 Progressive disclosure on the post-identifier screen

The screen first shows a passkey button with an "other methods" link. Clicking the link reveals password and OTP. Both screens are the same checkpoint. The link is a navigational choice that is finished explicitly, and the revealed screen is a new presentation.

```typescript theme={null}
// Initial presentation.
getTracker().authMethodsDecisionStarted({
  decisionName: "post-identifier",
  options: ["passkey-login-known-identifier", "other-methods"],
});

// The user clicks "other methods".
getTracker().authMethodsDecisionFinished({
  decisionName: "post-identifier",
  explicitDecisionValue: "other-methods",
});
getTracker().authMethodsDecisionStarted({
  decisionName: "post-identifier",
  options: ["password-login-known-identifier", "email-otp-login", "back"],
});
```

## 6. Best practices

* Assign every screen to a decision name, even single-option screens.
* Send `started` on presentation and whenever options change, not on re-render.
* Never finish a method choice by hand. Finish navigational choices immediately.
* Keep the order of the `options` array stable across renders.
* Keep `decisionName` values short, stable and independent of experiments or UI wording.

## 7. Next steps

* Continue with [Subflows](/corbado-observe/tracking/subflows) to learn about subflow types and their steps.
* Read [Modeling](/corbado-observe/tracking/modeling) for the full mapping method and a journey catalog.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.