Most Adobe Analytics implementations track what worked. The form submitted, the page loaded, the order completed. Errors get added later, usually after somebody asks why conversion dropped on the login page and nobody in the room can answer.

Authentication Analytics Whitepaper. Practical guidance, rollout patterns and KPIs for passkey programs.
Adobe has everything you need for that: a counter event for the error, a dimension holding the error code and a link call to send both without a page view. None of it is switched on by default. The way you set it up decides whether the report stays readable once real traffic hits it.
Sections 2 to 6 build the implementation: which events and dimensions to create, how to fire them without a page view, how to measure form abandonment now that the old plugin is gone and how to keep the error dimension out of Adobe's Low Traffic bucket. Section 7 is the part no tagging plan reaches, because on a login page a whole class of errors never touches your page at all.
Three kinds of failure get lumped together under "error" and each one needs different treatment.
| Class | Example | Who fixes it |
|---|---|---|
| Validation error | Email format rejected, password too short, one-time code expired | Product and UX |
| System error | Identity provider returns 500, rate limit hit on code requests | Engineering |
| User decision | Biometric prompt dismissed, passkey offer ignored | Nobody, until it is measured |
Mixed together they produce a chart that goes up and tells you nothing. Separate them as they arrive, with a dimension that names the class. Doing it later in a report is not possible, because the data no longer says which kind of error it was.
The third class is where authentication differs from a checkout form. A dismissed Face ID sheet is not a bug and it is not a neutral event either. It is a user who wanted to log in and did not.
One common case fits none of the three, and it is worth naming before you build the taxonomy: a one-time code requested and then never used. The message may have failed to arrive, it may have landed in a spam folder, or the user may have given up waiting. From the page all three look identical. Classifying it as a user decision would put a possible delivery failure in the wrong bucket, which is exactly what section 2 is trying to prevent. Keep it as its own drop-off, unclassified until another signal tells you which it was. Section 7 comes back to it.
Under Success events in report suite settings, create counter events:
| Event | Name | Fires when |
|---|---|---|
event10 | Error | Any tracked error surfaces |
event11 | Form Start | The user interacts with the first field of a form |
event12 | Form Complete | The form submits successfully |
Counter is the default type and simply counts the hits that carried the event. Keep the error event generic and let the dimensions carry the detail. Ten separate error events would cost ten slots in the report suite and tell you nothing a breakdown does not.
| Dimension | Holds | Expiry |
|---|---|---|
eVar20 | Error code, e.g. auth_invalid_credentials | Hit |
eVar21 | Error class: validation, system or user | Hit |
eVar22 | Flow step where it happened | Hit |
Hit expiry is the right default here, because an error belongs to the moment it happened. Let the dimension persist and the failure gets attached to every later hit in the visit, which inflates every breakdown built on it.
The third row is the one people skip. A custom link call carries no page name, so the page report cannot tell you where the error happened. Put the step of the flow into its own dimension instead.
A form that fails validation on four fields produces four errors at once. An eVar holds one value, so four eVar hits would misreport both the error count and the form count.
Use a list variable instead. It holds several values in one variable, separated by a delimiter you set in the report suite settings. Adobe gives you three of them, each value can be up to 255 bytes, there is no limit on the variable as a whole and it takes up to 250 unique values per visitor.
A list prop is the cheaper alternative, but the whole variable is capped at 100 bytes including the delimiters. That ceiling arrives faster than it sounds. Four codes the length of auth_invalid_credentials, 24 bytes each, already come to 99 bytes with the delimiters counted, so the fifth error on that hit is lost.
An error usually does not navigate, so a page view call is the wrong vehicle. Use a custom link call.
The s.tl() method takes three arguments: the element that was clicked, the type of link and a name for the report. Type o stands for a custom link, d for a download and e for an exit link. Adobe never tracks custom links on its own, so every error call has to be written by hand.
s.events = "event10"; s.eVar20 = "auth_invalid_credentials"; s.eVar21 = "system"; s.eVar22 = "login_submit"; s.linkTrackVars = "events,eVar20,eVar21,eVar22"; s.linkTrackEvents = "event10"; s.tl(true, "o", "Login Error");
Three things have to line up. 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 two lists are filters, not requirements. Adobe documents that an empty or undefined linkTrackVars sends all variables and an undefined linkTrackEvents sends all events. The moment you set them, as the snippet above does and as every tag manager does, they become the complete list of what is allowed through. Anything not named gets dropped.
That is the trap. Forget one entry and the call still goes out, the event is just missing from it and nothing anywhere warns you. It is the most common reason a correctly written error tracking implementation reports nothing at all.
Write eVar20 in linkTrackVars, not s.eVar20. The object identifier belongs in the assignment, not in the list.
With the Web SDK the same information travels in a sendEvent call:
alloy("sendEvent", { data: { __adobe: { analytics: { events: "event10", eVar20: "auth_invalid_credentials", eVar21: "system", eVar22: "login_submit", }, }, }, });
There are no allow-lists to forget here, so the trap from the previous section disappears with them.
Experiment with passkey flows in the Passkeys Debugger.
Adobe once shipped a Form Analysis plugin that flagged which field a user last touched before leaving a form. It did not survive the move to AppMeasurement.js and Adobe's plug-ins overview no longer carries it. Implementations that still reference it are usually running an archived copy.
Three pieces do the same job today:
For field-level detail, instrument focus and blur yourself and write the last field touched into a dimension on the abandonment call. That is more work than the old plugin and it is the version that survives a framework migration.
One caveat applied to the plugin and applies to your own version too: every abandonment sends an extra link call and Adobe counts each one as a server call. On a high-traffic login page, that is a conversation about your licence rather than a code review.
Error tracking breaks on cardinality more often than on bugs.
Adobe buckets dimension items under Low Traffic once a dimension exceeds its unique value threshold, by default 2,000,000 unique values per dimension, per report suite, per calendar month. Bucketed values are aggregated together instead of being reported individually.
The mechanism is easier to see than to describe. Three failures of the same kind collapse into one dimension item or into three:
Three rules keep the dimension under the threshold:
auth_invalid_credentials is a dimension value. "Sorry Jane, the password for jane@example.com is incorrect (ref 8f2c1a)" is a new value for every user and every attempt.Igor Gjorgjioski
Head of Digital Channels & Platform Enablement, VicRoads
We hit 80% mobile passkey activation across 5M+ users without replacing our IDP.
See how VicRoads scaled passkeys to 5M+ users, alongside their existing IDP.
Read the case studyEverything above assumes the error surfaces somewhere your code can see it. In authentication, a large class of failures does not.
Those five fail in different ways, so it helps to sort them before planning any work.
Three of the first four are within reach of a wrapper around the navigator.credentials call. A dismissed prompt and a cancellation through lost focus both make that call fail, so the wrapper catches them. The phone case is the third: the call completes back on the desktop, because the phone only stands in as the authenticator, so the outcome is measurable even though the middle of the journey is not.
The ignored autofill offer is the one case with nothing to catch. The call never finishes, it just sits there until something else ends it.
The one-time code case needs a different instrument again, because nothing throws and nothing fails. What makes it readable is the shape of the step: how many code requests were followed by a submitted code, how long the gap between the two was, and how often a second code was requested before the first one was used. None of that is an error your code catches. All of it is a drop-off you can count, and it applies to every SMS, email OTP and step-up MFA flow you run today, with or without passkeys in the picture.
So a wrapper buys you the attempt and the outcome. It does not buy you the reason. When the call does fail, the browser returns NotAllowedError whether the user cancelled, the login timed out or no passkey was on the device. The W3C declined to split that into separate codes, because telling the cases apart would let any site test whether a visitor has a passkey.
That single code is a container, not a reason. No vendor including us can open it. What separates the cases is what surrounded the call: how long it ran, whether the browser could offer a passkey at all, what the user did next. Those signals have to be collected while the login is running, because no report can reconstruct them later. WebAuthn errors covers the full taxonomy.
Everything up to section 6 makes the errors your page can see reportable. The section above is the remainder. In a login it is the part that matters most. Corbado Observe is the authentication observability layer that captures it, by measuring the passkey login itself instead of the page around it. It runs client-side next to your existing login, works with any identity provider and sends UUID-only telemetry with no PII.
NotAllowedError broken down by call duration, by client capability and by what the user did next.None of this waits for a passkey rollout. The same instrumentation reads your existing password, SMS, email OTP and MFA flows, which is where most teams find their first surprise. Your Adobe error tracking keeps doing what it is good at. The login itself gets measured where it happens.
Error tracking in Adobe Analytics is a solved problem for anything your code can catch. Create one generic error event, carry the detail in Hit-scoped dimensions, fire it through s.tl() with linkTrackVars and linkTrackEvents set correctly, use a list variable when a hit carries several errors and send codes instead of messages.
Then compare what your error report says against your identity provider and your support queue. The gap between them is made up of the failures from section 7, the ones that never reach a tag. That is usually the part to fix first. The authentication error rate definition and the authentication analytics playbook are the next two stops.
Corbado is the Passkey Intelligence Platform for large-scale CIAM teams running consumer authentication. We help you see what IDP logs and generic analytics tools can't: where passkeys, passwords, OTP, social login and fallback journeys succeed, stall or fail, which devices and browsers create friction, and when an OS update silently breaks login. Two products: Corbado Observe layers process mining and observability across authentication journeys. Corbado Connect adds managed passkeys with analytics built in alongside your IDP. VicRoads runs passkeys for 5M+ users with Corbado (+80% passkey activation). Talk to a Passkey Expert →
Create a counter event for the error, a dimension for the error type and fire both through a custom link call instead of a page view. With AppMeasurement 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. Set the error dimension to expire on Hit, because an error belongs to the moment it happened and not to the rest of the visit.
No. An eVar holds 255 bytes and Adobe buckets a dimension under Low Traffic once it exceeds 2,000,000 unique values per report suite per calendar month, so raw messages with identifiers or timestamps in them destroy the breakdown they were supposed to provide. Send a stable error code as the dimension value and keep the human-readable text in your own logs or in a classification.
Use a list variable. Adobe provides three of them, each value can be up to 255 bytes, the variable itself has no total byte limit and it supports up to 250 unique values per visitor. A form that fails validation on four fields at once is exactly the case list variables exist for, because four separate eVar hits would misreport both the error count and the form count.
Not on its own. The legacy Form Analysis plugin that flagged the last field a user touched did not survive the move to AppMeasurement.js and has no official replacement. What works today is a form start event, a form complete event and a calculated metric that subtracts one from the other, plus a field-level dimension if you instrument focus and blur yourself.
Because a large class of authentication failures never produces a page event. A cancelled biometric prompt, a passkey ceremony that times out, a WebAuthn call cancelled by focus loss and an ignored passkey autofill offer render nothing, navigate nowhere and click nothing. A wrapper around the WebAuthn call catches most of it: a dismissed prompt and a focus-loss cancellation reject the call, while a completed cross-device flow resolves it on the desktop. An ignored autofill offer produces nothing to catch, because the call stays pending. Either way this is an instrumentation question, not a reporting one.
Subscribe to our Passkeys Substack for the latest news.
Related Articles
Table of Contents