---
url: 'https://www.corbado.com/blog/track-login-success-rate-adobe-analytics'
title: 'Track Login Success Rate in Adobe Analytics'
description: 'The events, the calculated metric and the segment you need to track login success rate in Adobe Analytics, plus why the number comes out too high.'
lang: 'en'
author: 'Vincent Delitz'
date: '2026-08-13T08:34:49.701Z'
lastModified: '2026-08-13T11:58:13.775Z'
keywords: 'track login success rate adobe analytics, adobe analytics track successful login, track login events adobe analytics, adobe analytics login funnel'
category: 'Authentication'
---

# Track Login Success Rate in Adobe Analytics

## 1. Login Success Rate in Adobe Analytics is one Formula

Somebody wants to know what share of login attempts actually succeeds. They want it in Adobe Analytics rather than in a tool nobody has bought yet. The reporting side of that is genuinely small: login success rate is a calculated metric, successful logins divided by login attempts, formatted as a percentage.

## Key Facts

- **Login success rate requires two counter events:** an attempt fired before the outcome
  and a success fired only after authentication is complete.
- **Send both events without a page view** through AppMeasurement link tracking or the
  Adobe Experience Platform Web SDK.
- **Missing client-side failures make the rate look too high**, because a cancelled
  biometric prompt or ceremony timeout cannot lower a denominator it never entered.
- **Separate web and app report suites cannot produce one combined calculated metric**
  without solving the data combination upstream or in Customer Journey Analytics.

The work sits underneath it. Adobe can only divide two events that already exist, so somebody has to create them, fire them at the right moment and keep them in the same report suite. Get any of that wrong and you still get a number, which is the part that costs teams the most time.

### 1.1 What this article covers

Sections 2 to 7 are the implementation: the two definitions to agree on first, the events themselves, how to send them without a page view, the dimensions that let you break the rate down, the calculated metric and the workaround for a report suite that never got a login event. From section 8 the article turns to the four reasons the finished number usually looks better than what users experience.

## 2. Define a Login Attempt and a Login Success first

Settle two definitions before anyone writes code. When a login metric misleads, the definition is usually the reason rather than the implementation.

**What is an attempt?** The moment a user submits credentials or the moment the login form appears? The two produce very different denominators. Submission is the defensible one. A form that was only displayed is an impression.

**What is a success?** The moment the identity provider confirms the user or the page they land on afterwards? Count the landing page and you are counting navigation. Everyone who drops out between confirmation and landing leaves your numerator.

The same trap has a quieter version. If the success event fires on a page load somewhere in the middle of a multi-step flow, a user who abandons two steps later still counts as logged in. The aggregate looks healthy, the funnel underneath it is not. Write both definitions down and have the identity team confirm them, ideally before anyone builds a dashboard on the [login success rate definition](https://www.corbado.com/kpi/login-success-rate).

## 3. Create the Login Attempt and Login Success Events

Under Success events in your report suite settings, create two custom events and leave both as type Counter. A counter event counts how many hits carried it, which is all an attempt and a success need. The currency and numeric types exist for values you want to add up, such as order totals.

| Event    | Name          | Fires when                                                        |
| -------- | ------------- | ----------------------------------------------------------------- |
| `event1` | Login Attempt | The user submits credentials or triggers an authentication method |
| `event2` | Login Success | The identity provider confirms the authentication                 |

Optionally add a third for explicit failures:

| Event    | Name          | Fires when                               |
| -------- | ------------- | ---------------------------------------- |
| `event3` | Login Failure | The authentication returns a known error |

Do not expect `event1` to equal `event2 + event3`. It will not. Attempts go missing between the two. The last part of this article is about where they go.

## 4. Fire the Login Events without a Page View

A login rarely coincides with a page load, least of all in a single-page application. Use link tracking instead of a page view call.

With AppMeasurement:

```javascript
s.linkTrackVars = "events,eVar10,eVar11";
s.linkTrackEvents = "event1";
s.events = "event1";
s.eVar10 = "passkey"; // method offered or used
s.eVar11 = "checkout"; // entry point / context
s.tl(true, "o", "Login Attempt");
```

Three things have to line up for an event to survive a link call. [Adobe documents all three](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/config-vars/linktrackevents): the event has to be in `s.events`, `events` has to be listed in `linkTrackVars` and the specific event has to be listed in `linkTrackEvents`.

Those last two are allow-lists and they are where implementations break. Adobe documents that an empty or undefined `linkTrackVars` sends all variables and an undefined `linkTrackEvents` sends all events, so a bare implementation sends everything. Once you define them, which every tag manager does, they become the complete list of what may go out. Anything not named is dropped, the call still fires and nothing warns you. It is the most common reason a correctly written login event never appears in reporting.

The first argument of [`s.tl()`](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/functions/tl-method) is worth a second look. It expects the element the user clicked and it decides whether the request gets a head start before the browser leaves the page.

Pass `true` and you are telling AppMeasurement there is no element. The call goes out immediately, which is right for a login that stays on the same page. Pass a real element and AppMeasurement holds the page open for a moment so the request can leave first, which is what you want when the login redirects.

One thing to avoid: writing `this` in the hope that it resolves to the element. It only does so inside a handler bound to that element. In a login success callback it is usually `window` or `undefined`, so the redirect can still cut the request off. Either pass the element you actually have or hold the redirect until the call has gone out.

### 4.1 Send Login Events with the Web SDK

Two paths are in use. The `data.__adobe.analytics` object is the simpler one and Adobe describes it as the recommended way to set Analytics variables:

```javascript
alloy("sendEvent", {
    xdm: {
        eventType: "web.webinteraction.linkClicks",
        web: { webInteraction: { name: "Login Attempt", type: "other", linkClicks: { value: 1 } } },
    },
    data: {
        __adobe: {
            analytics: {
                events: "event1",
                eVar10: "passkey",
                eVar11: "checkout",
            },
        },
    },
});
```

The other path puts the same variables inside the XDM object under `_experience.analytics`. Large production implementations tend to emit this, so it helps to recognise it:

```javascript
xdm: {
  eventType: "web.webinteraction.linkClicks",
  web: { webInteraction: { name: "Login Attempt", type: "other", linkClicks: { value: 1 } } },
  _experience: {
    analytics: {
      event1to100: { event1: { value: 1 } },
      customDimensions: {
        eVars: { eVar10: "passkey", eVar11: "checkout" },
        props: { prop5: "logged_out" }
      }
    }
  }
}
```

If both are set for the same variable, the `data` object wins.

### 4.2 Send Login Events with Adobe Tags

Wrap either call in a Direct Call Rule so the application only announces what happened:

```javascript
_satellite.track("login_attempt", { method: "passkey", entryPoint: "checkout" });
```

Then map the rule to the Analytics action. The tracking logic stays out of the application release cycle, which matters when the authentication team ships on a different cadence than the tagging team.

### 4.3 Wrap the Passkey Ceremony so Failures fire too

The calls above send the attempt. They send nothing when the attempt fails. That is the gap the second half of this article keeps coming back to.

The fix is to put the tracking around the authentication call instead of next to it. That call is the passkey ceremony: the moment your page hands over to the browser, the browser shows Face ID or Windows Hello and hands back either a credential or an error. It either resolves or rejects, so both outcomes can fire an event:

```javascript
async function loginWithPasskey(requestOptions) {
    track("event1", { method: "passkey" }); // attempt, before the outcome is known
    const startedAt = performance.now();

    try {
        const credential = await navigator.credentials.get(requestOptions);
        track("event2", { method: "passkey" }); // success
        return credential;
    } catch (err) {
        track("event3", {
            method: "passkey",
            code: err.name, // NotAllowedError, AbortError, SecurityError
            durationMs: Math.round(performance.now() - startedAt),
        });
        throw err;
    }
}
```

`track()` stands for whichever of the calls above your implementation uses. Three details in that shape matter.

The attempt fires before the `await`, not after it. An attempt recorded once the outcome is known can only ever describe successes.

Put the error in as `err.name`, a short code, not as `err.message`, a full sentence. Free text blows up the number of distinct values in the dimension, which is the cardinality problem in the next section. Keep in mind what the code is worth on its own: `NotAllowedError` is what the browser returns for a user cancelling, for the ceremony timing out and for no passkey being available, all three.

The duration is what separates those cases. A rejection after 400 milliseconds is somebody dismissing a prompt, a rejection after three minutes is a timeout. Sort the value into buckets such as "under 1s" and "over 60s" before it reaches a dimension. Raw millisecond values would be a new dimension item every single time.

## 5. Break the Login Success Rate down by Method and Entry Point

One overall success rate tells you almost nothing. The same number, broken down, tells you where to look.

| Variable                   | Purpose                                     | Suggested settings                                |
| -------------------------- | ------------------------------------------- | ------------------------------------------------- |
| `eVar10` Login Method      | password, passkey, OTP, social, SSO         | Expiration: Visit. Allocation: Most Recent (Last) |
| `eVar11` Login Entry Point | checkout, account, deep link, gated content | Expiration: Visit                                 |
| `eVar12` Login Error Code  | the error the application surfaced          | Expiration: Hit                                   |
| `prop5` Login Status       | logged in / logged out, set on every hit    | Enable pathing for the transition report          |

One production implementation we analysed writes eligibility into a prop on every login page view, as `passkey_eligible:true` or `passkey_eligible:false`. That costs one variable and answers a question that otherwise gets lost: could this user have used a passkey in the first place?

Two things to watch when you fill these variables.

The first is length. An eVar holds 255 bytes and cuts off anything beyond that, so a raw error message does not belong in one. Store a short code and map it to readable text later with a classification. Props are tighter still at 100 bytes.

The second is the number of distinct values. Adobe moves dimension items into a bucket called [Low Traffic](https://experienceleague.adobe.com/en/docs/analytics/technotes/low-traffic) once the dimension exceeds its threshold, by default two million unique values per dimension, per report suite, per month. Everything in that bucket is lumped together instead of reported on its own. A session ID or a raw error string in an eVar gets you there fast. The failure is silent: the breakdown stops breaking anything down while the calculated metric on top of it keeps returning a number that looks fine.

## 6. Build the Login Success Rate as a Calculated Metric

In Analysis Workspace, create a calculated metric:

```
Login Success Rate = event2 (Login Success) / event1 (Login Attempt)
```

Drag both metrics into the definition, put the divide operator between them, then set Format to Percent and pick your decimal places. Format offers Decimal, Time, Percent and Currency.

![The Adobe Analytics calculated metric builder with a rate defined as one metric divided by another](https://www.corbado.com/website-assets/adobe_analytics_calculated_metric_builder_7fbf4f2b03.png)

The builder above shows the same shape with different components. Swap in Login Success and Login Attempt, switch Format from Decimal to Percent and the metric is done.

For the rate of a single method without touching the implementation, use a segmented metric: apply a segment such as `Login Method equals passkey` to each component inside the metric definition instead of filtering the whole panel. Password, passkey and OTP success rates then sit side by side in one table.

## 7. Report Logins when no Login Event exists

This happens more often than it should and there is a documented workaround. The Adobe Experience League community sees it regularly: [a team needs daily, monthly and rolling 30-day login reports](https://experienceleaguecommunities.adobe.com/t5/adobe-analytics-questions/login-reports-for-analytics-without-an-event/td-p/642208) and finds out nobody ever implemented a login event.

If a login status variable is set on every hit, a sequential segment can infer the login from the state change:

```
Visit container, Only Before Sequence:
  Logged In Status equals "no"
  THEN (After 1 Hit)
  Logged In Status equals "yes"
  AND Logged In Status equals "yes"
```

Two parts of that segment do the work. `THEN` makes it sequential, so the two conditions have to happen in that order instead of just both being true. "Only Before Sequence" limits the result to the hits up to and including the start of the sequence, which stops the segment from returning the whole logged-in remainder of the visit.

Run it with the Occurrences metric and you get an approximation of how often somebody went from logged out to logged in. Approximation is the right word: the analyst who published this compared it against a real login event on her own site and got close numbers, not identical ones. Do the same comparison before the figure goes into a report.

And be clear about what you now have. The segment reconstructs logins from a variable that describes the page. That is not the same as measuring an authentication. Failed attempts never show up, because a failed attempt changes no state. Without them there is no denominator, so the only rate you can build is successes per visit. [Login reports in Adobe Analytics without an event](https://www.corbado.com/blog/adobe-analytics-login-report-without-event) has the full walkthrough.

### 7.1 Web and App in separate Report Suites cannot be combined

Teams usually run into this one late, when somebody asks for the login rate across all channels.

If web and native app are instrumented in separate report suites, Adobe cannot calculate a combined login success rate. That is [documented behaviour](https://experienceleague.adobe.com/en/docs/analytics-learn/tutorials/analysis-workspace/using-panels/multiple-report-suites-in-analysis-workspace): data from multiple report suites cannot be combined in tables, segments or calculated metrics. Analysis Workspace shows two report suites side by side in one project through panel-level suite selection, but it will not divide one by the other.

That is where the second dashboard and the spreadsheet come from. A single cross-channel login rate has to be solved at the data collection layer, with a global report suite or in Customer Journey Analytics.

## 8. Why the Login Success Rate comes out too high

The metric is built and the number looks good. Four things keep it looking better than what users are experiencing.

### 8.1 Failed Attempts that never fire an Event

The denominator holds the attempts your tag learned about. Modern authentication fails in ways that render no error page and navigate nowhere:

- The user cancels the Face ID or Windows Hello prompt.
- The user switches to their password manager app. The Web Authentication API cancels a pending `create()` or `get()` call when the browser window loses focus, by design, for security reasons.
- The ceremony runs out of time. The specification recommends a timeout of five to ten minutes and defaults to five, while production deployments often set three. Browsers may override a value they consider unreasonable, so the timeout that applies is not always the one you configured.

The same hole exists without any passkey involved. A user who requests an SMS or email code, leaves for their inbox and never comes back has made an attempt that your page recorded as a code request and nothing else. Whether the message arrived, whether it was read and whether the user gave up waiting all happened somewhere your tag cannot follow. If your attempt event fires when the code is requested, that user sits in the denominator with no outcome. If it fires when the code is submitted, the attempt never existed.

What your metric sees depends on where the events fire. The three common starting points fail differently.

**If you wrapped the ceremony** as shown in the snippet above, `event1` fires the moment the method is triggered and the rejection handler fires `event3`. The attempt sits in the denominator and the failure arrives with a code and a duration attached. All three failures above end in a rejected `navigator.credentials` call, so all three can be measured. If the wrapper is missing, adding it is the whole fix.

**If your attempt event fires on a form submit**, a ceremony that never submits a form produces no attempt at all. Passkey autofill is the clearest case: the user is logged in without touching your submit button. A cancelled prompt on that path leaves nothing behind either. The attempt is missing from the denominator and the failure from the numerator, so the rate does not move.

**If your attempt event fires on the login page load**, the attempt is counted and a cancelled prompt does pull the rate down. What you do not get is a reason. No failure event, no method, no error code, nothing that separates a broken device from an OS update from users changing their minds. The number moves and nobody can act on it. This version wastes the most time, because it looks like working instrumentation.

The first two are easier to tell apart side by side. The difference decides what the metric can ever tell you:

So `event1` rarely equals `event2 + event3`. Without a wrapper the gap is made up of failures nobody sent an event for. With the wrapper it turns into a number you can put on a dashboard, broken down by error code and by how long the call ran.

Three things stay invisible even with the wrapper in place: a passkey offered through autofill and ignored, the reason behind a rejection and the phone half of a cross-device login. The next three sections say what each one does to the rate. [Why Adobe Analytics cannot debug login failures](https://www.corbado.com/blog/why-adobe-analytics-cannot-debug-login-failures) works through the mechanics behind all three.

### 8.2 Passkey Autofill leaves no Trace on the Page

Passkey autofill, or conditional mediation, starts a `get()` call that then waits. Nothing appears until the user taps a field marked `autocomplete="username webauthn"`. The dropdown that follows is drawn by the browser on top of your page rather than in it. No element, no click handler, no submit and no navigation. [Activity Map](https://experienceleague.adobe.com/en/docs/analytics/analyze/activity-map/faq) misses it too, because it tracks links, form submits, buttons and elements carrying an `s_objectID` or a `tl()` call.

A wrapper does not rescue this one either, since an ignored offer produces no rejection to catch. It is the one failure that stays out of the denominator wherever you fire the attempt event.

### 8.3 NotAllowedError hides the Reason for the Failure

A rejection you did catch still arrives without a reason. The [specification](https://www.w3.org/TR/webauthn-3/) has the browser return `NotAllowedError` for a user cancellation, for the timer expiring and for no eligible credential on the device alike, deliberately, so that no site can probe whether a visitor has a passkey registered.

For the metric that means a failure count is within reach in Adobe and a failure breakdown is not. This applies to every vendor including us: **no tool can read the reason out of the error object.** It can only be reconstructed from what surrounded the call, above all how long the call ran, since half a second looks like somebody cancelling and five minutes looks like a timeout.

### 8.4 Cross-Device Logins lose the Middle of the Journey

Some logins start on a desktop and finish on a phone. The outcome still lands on the desktop, because the phone stands in as the authenticator instead of opening a session of its own, so the `get()` call resolves there and a success event can fire.

What the rate loses is the middle: what the phone showed, how long the user hesitated, whether they scanned at all. An abandoned cross-device flow is the harder case, because it ends in a rejection your page only sees if somebody wrapped the call.

## 9. Three Checks on your own Login Data

None of this makes your Adobe implementation wrong. It means the login success rate answers a narrower question than its name suggests: of the login attempts the page could observe, how many produced a success the page could observe.

Three checks you can run this week without buying anything:

1. **Compare your attempt count with the request count from your identity provider.** Where the page recorded an attempt and the identity provider never saw a request, something failed in between. Where neither system recorded anything, the attempt was never visible from the page at all, which is the case this whole section describes.
2. **Check `event1` against `event2 + event3`.** A large gap is a reason to investigate, not a diagnosis. Rule out the ordinary causes first: an outcome beacon that got dropped, a duplicate attempt event, a redirect that kills the call, different consent handling on the two events, definitions that drifted apart. Whatever remains is the invisible kind of failure.
3. **Break the rate down by method.** If passkeys look far better than passwords, they are usually not performing miracles. Their failures are simply not being counted.

If those checks turn up a gap you care about, a better report will not close it. Measuring the ceremony itself will.

## 10. How Corbado can help

Every gap above comes back to the same thing: the metric can only count what the page could see. [Corbado Observe](https://www.corbado.com/observe) is the authentication observability layer that measures the ceremony instead of the page around it. It runs client-side next to the login you already have, works with any identity provider and sends UUID-only telemetry with no PII, so your Adobe implementation keeps doing its job on the page level.

[Video: Login funnel](https://www.corbado.com/videos/features/login-funnel.mp4)

- **A denominator that contains the failures:** the attempt is counted at the `navigator.credentials` call, so cancels, focus loss and timeouts appear [in the funnel](https://www.corbado.com/observe/login-funnel) instead of disappearing from both sides of the fraction.
- **Success rate per method, permanently:** passkey against password against OTP [side by side](https://www.corbado.com/observe/login-methods), so the third check above becomes a view you keep instead of a one-off segment.
- **The gap explained:** the same `NotAllowedError` broken down by how long the call ran, by what the browser was capable of and by what the user did next, on [the error page](https://www.corbado.com/observe/passkey-errors).
- **Changes measured instead of estimated:** after a change in the login, [method uplift](https://www.corbado.com/observe/method-uplift) shows the before and after for the affected segment instead of a wobble in a weekly aggregate.
- **Useful before any passkey ships:** the same funnel reads your current password, SMS and email OTP and step-up MFA flows, so the rate gets an honest denominator on the login you run today.

The calculated metric stays in Adobe. What it cannot see gets measured where it happens.

## 11. Conclusion: build the Metric, then check what it cannot see

The calculated metric is the easy part and takes five minutes. What decides whether the number is worth anything is the rest: the two definitions, the report suite question and the failures that never fire an event.

For the background on why client-side authentication escapes page instrumentation, [why Adobe Analytics cannot debug login failures](https://www.corbado.com/blog/why-adobe-analytics-cannot-debug-login-failures) covers the same ground from the failure side. The [authentication analytics playbook](https://www.corbado.com/blog/authentication-analytics-playbook) has the wider metric set.

## Frequently Asked Questions

### How do I calculate login success rate in Adobe Analytics?

Create two counter events in your report suite settings, one for Login Attempt and one for Login Success, fire both through link tracking instead of page views, then build a calculated metric that divides success by attempt and format it as a percentage. The formula is the easy part. The work sits in defining what counts as an attempt and making sure both events fire at the right moment in the same report suite.

### Why does my login success rate look too high in Adobe Analytics?

Because failed attempts that never fired an event are missing from both sides of the fraction. Cancelled biometric prompts, focus-loss cancellations and ceremony timeouts produce no page event at all, so if your events fire on page loads they cannot lower the rate. Wrapping the authentication call and dispatching on the rejection makes them visible, which is usually the fix. That is how a healthy-looking login success rate ends up sitting on top of a much worse user experience.

### Can I track login events without a page view in Adobe Analytics?

Yes. Use `s.tl()` in AppMeasurement or a `sendEvent` call with the Web SDK. With AppMeasurement three things have to line up: the event has to be set in the events variable, events has to be listed in linkTrackVars and the specific event has to be listed in linkTrackEvents. Both of those are filters, so an undefined or empty list includes everything, but once a tag manager defines them, anything not named is dropped from the call without an error.

### How do I report on logins if there is no login event implemented?

Use a sequential segment on a login status variable that matches a hit where the status is no, followed by a hit where the status is yes, counted with the Occurrences metric. This infers logins from the state change and is the accepted workaround in the Adobe community. It cannot see failed attempts, because a failed attempt produces no state change, so the only rate you can build on top of it is successes over visits.

### Why can I not calculate one login success rate across web and app?

Because Adobe cannot combine data from multiple report suites in a table, segment or calculated metric. If login is instrumented in separate report suites for web and app, Analysis Workspace can display both suites side by side using panel-level suite selection but cannot divide one by the other. A single combined rate has to be solved at data collection time with a global report suite or in Customer Journey Analytics.
