Monitoring web app verification
Learn which analytic events a remote web app verification session emits at each step, how to tell a same-device session from a cross-device one, and which parts of the flow MATTR VII cannot observe.
This page maps the remote web app verification workflow onto the analytic events MATTR VII emits, so you can tell at a glance how far a verification session progressed.
It covers both delivery modes, same-device and cross-device. Most of the flow is identical between them, so the sections below describe the shared path and mark the steps, events, and identifiers that belong to only one mode. It does not cover the Digital Credentials API, which reaches MATTR VII differently.
It assumes you are familiar with how to monitor a flow, which covers the event structure, the identifiers, and the general method for investigating a failure. This page covers only what is specific to this flow.
Analytic events matter more here than in most flows. Your own code calls the Verifier Web SDK rather than the presentation endpoints, so the exchanges between the SDK, the wallet, and MATTR VII are not visible in your application logs at all. The events are the only server-side record of them.
Telling same-device from cross-device apart
You will usually be looking at a session without knowing which mode it ran in. The verifier application can be configured to allow either mode or both, and the SDK chooses per session, so the mode is a property of the session rather than of your tenant.
MATTR VII decides the mode from one thing: whether a redirectUri was supplied when the session was
created. A session created with one is same-device, because the holder must be returned to that URI
on the device they started on. A session created without one is cross-device, and an origin is
required instead so the request web view can be framed by your page.
That decision is recorded in the events. Read CREDENTIAL_PRESENTATION_SESSION_CREATE_START:
data on session creation | Mode |
|---|---|
redirectUri present | Same-device |
redirectUri absent, origin present | Cross-device |
Neither field is removed by sanitization, so this works on any tenant. Three later signals confirm it:
CREDENTIAL_PRESENTATION_SESSION_REQUEST_CREATE_SUCCESSappears only in cross-device sessions. It is the QR code being produced and then refreshed, which same-device has no equivalent of.CREDENTIAL_PRESENTATION_SESSION_LOAD_SUCCESScarriesauthRequestUriin both modes, but the response differs. In cross-device it serves the request web view that displays the QR code. In same-device it redirects the browser straight into the wallet.- A session with several
sessionRequestIdvalues is cross-device. Same-device uses the one authorization request created with the session.
What you can observe
A verification is a sequence of separate exchanges, and only some of them reach MATTR VII.
| Step | Mode | Reaches MATTR VII | Emits events |
|---|---|---|---|
| 1. Session creation by the Verifier Web SDK | Both | Yes | Yes |
| 2. Session URL loaded, framing the QR view or redirecting into the wallet | Both | Yes | Yes |
| 3. Authorization request refresh | Cross-device | Yes | Yes |
| 4. Holder scans the QR code | Cross-device | No | No |
| 5. Wallet is invoked on the same device | Same-device | No | No |
| 6. Wallet retrieves the authorization request object | Both | Yes | Yes |
| 7. Holder reviews the request and consents in their wallet | Both | No | No |
| 8. Wallet submits the authorization response | Both | Yes | Yes |
9. Wallet redirects the holder back to your redirectUri | Same-device | No | No |
| 10. Verifier Web SDK polls the session status | Cross-device | Yes | No |
| 11. Result retrieval | Both | Yes | Yes |
Step 7 is the blind spot that causes most confusion, in both modes. Consent happens entirely in the wallet, so a holder who reads the request and declines it produces exactly the same evidence as a holder who put their phone down. In both cases the last event is a successful request retrieval and nothing follows it.
Steps 4, 5, and 9 are invisible for the same reason: they happen on the holder's device, between the operating system and the wallet, without a request to MATTR VII. In same-device this matters twice over, because both the hand-off into the wallet and the hand-back to your site are unobserved.
Step 10 does reach MATTR VII, but the status endpoint emits no analytic events. The SDK can be polling steadily while the events show nothing at all, so a gap in the events between steps 8 and 11 is not a sign that the SDK stopped.
Workflow and the events it emits
The following diagrams extend the detailed workflow with the events emitted at each hop.
A verification emits other events alongside these, recording key management and signing work MATTR VII performs on your behalf. Refer to the Events registry for the full catalog.
The events that matter
These are the events that tell you something about the journey.
| Event | Mode | What it tells you | Identifiers in data |
|---|---|---|---|
CREDENTIAL_PRESENTATION_SESSION_CREATE_START | Both | A verification session was requested, and which mode it will run in | applicationId, redirectUri, origin, userAgent, state |
CREDENTIAL_PRESENTATION_SESSION_CREATE_SUCCESS | Both | The session exists | sessionId, applicationId, applicationType, sessionType, state |
CREDENTIAL_PRESENTATION_SESSION_LOAD_SUCCESS | Both | The browser reached the session URL, and was shown the QR view or sent into the wallet | sessionId, authRequestUri |
CREDENTIAL_PRESENTATION_SESSION_REQUEST_CREATE_SUCCESS | Cross-device | A QR code was produced, or refreshed | sessionId, sessionRequestId, expiresIn |
CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_START | Both | Something asked for the request object | sessionId, sessionRequestId |
CREDENTIAL_PRESENTATION_SESSION_REQUEST_SIGN_SUCCESS | Both | MATTR VII signed the request object with your verifier certificate | sessionId, sessionRequestId, verifierSignerId |
CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_SUCCESS | Both | A wallet received the signed request object | sessionId, sessionRequestId, authRequestObject.state |
CREDENTIAL_PRESENTATION_SESSION_RESPONSE_PROCESS_START | Both | A wallet posted an authorization response | sessionId |
MOBILE_CREDENTIAL_PRESENTATION_VERIFY_SUCCESS | Both | The response was decrypted and the credentials in it were checked | numberOfCredentials, isUsingEcosystemTrustedIssuers |
CREDENTIAL_PRESENTATION_SESSION_RESPONSE_PROCESS_SUCCESS | Both | The result is stored and ready to be collected | sessionId, applicationId, applicationType |
CREDENTIAL_PRESENTATION_SESSION_ABORT_SUCCESS | Both | The session was abandoned deliberately rather than left to expire | sessionId |
CREDENTIAL_PRESENTATION_SESSION_RESULT_EXCHANGE_SUCCESS | Both | Your web application collected the result over the front channel | sessionId, state, error |
CREDENTIAL_PRESENTATION_SESSION_RESULT_RETRIEVE_SUCCESS | Both | Your backend collected the result over the back channel | sessionId, error |
Three of these are worth singling out:
CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_SUCCESSis the only evidence that a wallet actually received the request. In cross-device it is the proof that the QR code was scanned. In same-device it is the proof that the deep link resolved to a wallet rather than dead-ending in the browser.CREDENTIAL_PRESENTATION_SESSION_RESULT_EXCHANGE_SUCCESSis the terminal event for a front-channel session, and the one to join on. It carries yourstatevalue alongside thesessionId, and where the presentation failed it carries the sameerror.typeyour application received. Its back-channel counterpart,CREDENTIAL_PRESENTATION_SESSION_RESULT_RETRIEVE_SUCCESS, carries theerrorbut not thestate.CREDENTIAL_PRESENTATION_SESSION_ABORT_SUCCESSis what separates a holder who gave up from a holder who never started. Without it, both look like a session that stopped partway.
CREDENTIAL_PRESENTATION_SESSION_REQUEST_CREATE_SUCCESS embeds the QR code as an SVG in its data,
so cross-device sessions produce large events. Keep limit low when you query a range that includes
them.
A processed response is not a verified credential
CREDENTIAL_PRESENTATION_SESSION_RESPONSE_PROCESS_SUCCESS and
MOBILE_CREDENTIAL_PRESENTATION_VERIFY_SUCCESS mean the wallet's response was decrypted and the
credentials in it were checked. They are emitted just the same when a credential comes back with
verified: false, or when the holder did not hold the requested credential at all.
The events tell you the exchange completed. Only the session result tells you the outcome, as described in Understanding the verified flag.
Fields removed before storage
Some fields are removed from an event before it is stored, so an event may return less than the operation handled. In this flow the removals cover everything the holder actually presented:
- The
credentialQueryis removed fromCREDENTIAL_PRESENTATION_SESSION_CREATE_START, and thepresentation_definitionis removed from theauthRequestObjectonCREDENTIAL_PRESENTATION_SESSION_RETRIEVE_SUCCESS. You can confirm a session was created and a request was collected, but not what was asked for. - The per-credential results are removed from
MOBILE_CREDENTIAL_PRESENTATION_VERIFY_SUCCESS, leaving onlynumberOfCredentials. - The credentials, claims, and credential errors are removed from both result events, leaving the
sessionId, thestate, and the failureerrorwhere there was one.
Refer to Sanitized events.
Failure events
Each of these families follows the START, SUCCESS, FAIL pattern, and the FAIL variants carry
data.error.type and data.error.message. The ones worth looking for are:
| Event | Mode | Use it to find |
|---|---|---|
CREDENTIAL_PRESENTATION_SESSION_CREATE_FAIL | Both | A session that was never started, including one asking for a mode the verifier application does not allow |
CREDENTIAL_PRESENTATION_SESSION_LOAD_FAIL | Both | A session URL that could not be served, for example one for a session that has already ended |
CREDENTIAL_PRESENTATION_SESSION_REQUEST_CREATE_FAIL | Cross-device | A QR code that could not be produced or refreshed |
CREDENTIAL_PRESENTATION_SESSION_REQUEST_SIGN_FAIL | Both | A problem with the verifier signer certificate used to sign the request object |
CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_FAIL | Both | A wallet that could not collect the request object, most often because that request had already been used or replaced by a refresh |
CREDENTIAL_PRESENTATION_SESSION_RESPONSE_PROCESS_FAIL | Both | A response MATTR VII could not decrypt or accept |
MOBILE_CREDENTIAL_PRESENTATION_VERIFY_FAIL | Both | A response whose contents could not be verified at all |
CREDENTIAL_PRESENTATION_SESSION_RESULT_EXCHANGE_FAIL | Both | A front-channel result collection that was rejected, for example a reused response code or a mismatched challenge |
CREDENTIAL_PRESENTATION_SESSION_RESULT_RETRIEVE_FAIL | Both | A back-channel result retrieval that was rejected |
ECOSYSTEM_CONFIG_RETRIEVE_FAIL appears on the same request ID as every response you process, with an
error.type of ConfigNotExists, on any tenant that has no ecosystem configuration. MATTR VII checks
for one on every verification and falls back to your own
trusted issuers when there is none. It is expected, and
it does not mean the verification failed. MOBILE_CREDENTIAL_PRESENTATION_VERIFY_SUCCESS records
which list was used, in data.isUsingEcosystemTrustedIssuers.
Identifiers in this flow
The identifiers common to every flow
apply here, with one difference worth noting: the sessionId in this flow is the presentation session
identifier that your application already holds, not the internal session identifier described on that
page. It sits inside the event data in the same way, so it is still not directly filterable.
| Identifier | What it identifies | Where you first get it |
|---|---|---|
sessionId | One verification session, from creation to result | The Verifier Web SDK response, and every session event |
sessionRequestId | One authorization request | CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_SUCCESS, and on every refresh in cross-device |
applicationId | The verifier application the session ran against | Your verifier application configuration |
state | Your own correlation reference | You supply it when starting the session |
verifierSignerId | The certificate used to sign the request object | Your verifier signer certificates |
A single verification spans at least four separate exchanges, so it has at least four different
request IDs. The sessionId is what ties them together.
In cross-device there will usually be more than one sessionRequestId for a session. The QR code is
refreshed while the holder is deciding, and each refresh creates a new authorization request. A holder
who scans a code that has since been refreshed produces a
CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_FAIL against the old sessionRequestId rather than against
the session. Same-device sessions use the single request created with the session, so a second
sessionRequestId there is unexpected.
Correlating a session with your own records
Supply a state value when you start the session. It is the only identifier in this flow that means
anything in your system, it survives the round trip to the wallet, and it comes back on both success
and failure in both modes. Refer to
Correlating verification sessions.
In the events, state appears on CREDENTIAL_PRESENTATION_SESSION_CREATE_START and
CREDENTIAL_PRESENTATION_SESSION_CREATE_SUCCESS at the start, as authRequestObject.state on
CREDENTIAL_PRESENTATION_SESSION_RETRIEVE_SUCCESS in the middle, and on
CREDENTIAL_PRESENTATION_SESSION_RESULT_EXCHANGE_SUCCESS at the end. Where you did not supply one,
authRequestObject.state falls back to the sessionId.
Knowing when a result is ready
There is no webhook for a verification session. The only MATTR VII event types you can subscribe to are the two OID4VCI issuance types, so nothing pushes a verification outcome to you. How your application finds out instead depends on the mode, and neither route is visible in the events:
- Cross-device: the Verifier Web SDK polls the session status on your behalf and resolves the call that started the session. The status response carries the response code once the result is ready.
- Same-device: MATTR VII returns a redirect URI to the wallet rather than putting the response code
on the status endpoint, and the wallet uses it to send the holder back to your site with the code in
the URL fragment. Your application calls
handleRedirectCallback()to pick it up. The response code is deliberately withheld from the status endpoint in this mode, so that it can only reach a browser on the same device as the wallet.
Either way, do not use the Events API as a substitute. Use the events to explain a session after the fact, and let the SDK, or your backend calling the result endpoint, tell you when a result exists.
Next steps
How would you rate this page?
Last updated on