Monitoring the OID4VCI Authorization Code flow
Learn which analytic events the OID4VCI Authorization Code flow emits at each step, which identifiers correlate them, and which parts of the flow MATTR VII cannot observe.
This page maps the OID4VCI Authorization Code flow onto the analytic events MATTR VII emits, so you can tell at a glance how far an issuance attempt progressed.
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.
What you can observe
An Authorization Code issuance is a long chain of exchanges, and the holder leaves MATTR VII twice along the way.
| Step | Reaches MATTR VII | Emits events | Notes |
|---|---|---|---|
| 1. Offer creation by your backend | Yes | Yes | Optional in this flow. Offer events exist only if you created the offer through MATTR VII. A wallet or SDK that builds the authorization request URL itself skips this step. |
2. Wallet fetches issuer metadata from the .well-known endpoints | Yes | No | |
| 3. Wallet reads the offer and prepares the authorization request | No | No | |
| 4. Authorization request at the authorization endpoint | Yes | Yes | |
| 5. Holder authenticates with your authentication provider | No | At the handoff only | OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_START is emitted as MATTR VII redirects the holder out. The authentication itself emits nothing. |
| 6. Authentication provider callback | Yes | Yes | |
| 7. Holder completes your interaction hook, where one is configured | No | At the handoff only | OPENID_INTERACTION_HOOK_START is emitted as MATTR VII redirects the holder out. The interaction itself emits nothing. |
| 8. Interaction hook callback | Yes | Yes | |
| 9. Token exchange at the token endpoint | Yes | Yes | |
| 10. Credential request at the credential endpoint | Yes | Yes |
Steps 5 and 7 are the two blind spots that matter. Both happen on systems you own rather than on MATTR VII, so an attempt that stalls there produces no further events. Analytic events cannot tell you whether the holder abandoned your login page, failed a check in your interaction hook component, or never arrived at either.
To see inside those spans, instrument them yourself and record the interactionId MATTR VII hands
you on the way in. Your authentication provider receives it as the OAuth state parameter on the
authorization request. Your interaction hook component receives it as the state claim in the
session token. Logging that value alongside your own traces lets you line them up with
data.interactionId on the MATTR VII events either side of the gap.
The authorization phase is also not a single request. Steps 4 to 8 are a chain of browser redirects, and several of the hops in that chain reach MATTR VII without emitting an event of their own. Treat the events below as markers for the phase rather than a record of every hop.
Workflow and the events it emits
The following diagram extends the Authorization Code workflow with the events emitted at each hop:
An issuance emits other events alongside these, recording 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 | Step | What it tells you | Identifiers in data |
|---|---|---|---|
OPENID_OFFER_CREATE_SUCCESS | 1 | The offer URI exists | uri |
OPENID_AUTHORIZE_START | 4 | A wallet began an authorization request | client_id, scope, redirect_uri, state |
OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_START | 5 | MATTR VII is handing the holder to your authentication provider | sessionId, interactionId |
USER_CREATE_SUCCESS | 6 | A new user record was created for this holder | id, authenticationProvider.providerId |
USER_UPDATE_SUCCESS | 6 | An existing user record was matched instead, and its claims refreshed | id, authenticationProvider.providerId |
OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_SUCCESS | 6 | The holder authenticated and MATTR VII resolved them to a user | sessionId, userId, interactionId, existingUser |
OPENID_INTERACTION_HOOK_START | 7 | MATTR VII is handing the holder to your interaction hook | sessionId, userId, interactionId |
OPENID_INTERACTION_HOOK_SUCCESS | 8 | Your hook returned a valid response and the holder was let through | sessionId, userId, interactionId |
OPENID_AUTHORIZE_SUCCESS | 8 | The authorization phase completed and an authorization code was issued | sessionId, userId, login.interactionId, interactionHook.interactionId, existingUser, existingSession |
OPENID_TOKEN_START | 9 | A wallet presented an authorization code | sessionId, userId, client_id, grant_type, redirect_uri |
OPENID_TOKEN_SUCCESS | 9 | The wallet holds an access token | sessionId, userId, scope, expires_in |
OPENID_CREDENTIAL_START | 10 | The wallet asked for the credential | sessionId, userId, clientId, format |
OPENID_CLAIM_SOURCE_RETRIEVE_START | 10 | MATTR VII is calling your claims source | sessionId, userId, claimSourceId |
OPENID_CLAIM_SOURCE_RETRIEVE_SUCCESS | 10 | Your claims source returned usable claims | sessionId, claimSourceId, claimSource.url |
USER_CREDENTIAL_CREATE_START | 10 | MATTR VII began building the credential record | sessionId, userId, credentialId, credentialConfigurationId |
USER_CREDENTIAL_CREATE_SUCCESS | 10 | The credential record exists | sessionId, userId, credentialId, credentialConfigurationId |
OPENID_CREDENTIAL_SUCCESS | 10 | The credential was issued and returned to the wallet | sessionId, userId, credentialId, credentialConfigurationId, clientId |
Two of these are worth singling out:
OPENID_AUTHORIZE_SUCCESSis the pivot event of this flow. It is the only event that carries both interaction IDs, the login one and the interaction hook one, alongside thesessionIdanduserIdthat the rest of the journey uses. If you have any identifier from the authorization phase, this is the event that connects it to everything that follows.USER_CREDENTIAL_CREATE_SUCCESScarriessessionId,userId,credentialId, andcredentialConfigurationIdtogether, which makes it the best place to join a MATTR VII issuance to a record in your own system.
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 cluster around the holder's identity and claims:
- The claims your authentication provider returned, and the holder's
subjectIdat that provider, are removed fromOPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_SUCCESSandOPENID_AUTHORIZE_SUCCESS. - The claims your interaction hook returned are removed from
OPENID_INTERACTION_HOOK_SUCCESS. - The claims your claims source returned are removed from
OPENID_CLAIM_SOURCE_RETRIEVE_SUCCESS.
You can therefore confirm that each of these steps ran, and see which provider, hook, or claims source was used, but you cannot read the claims themselves. 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 | Use it to find |
|---|---|
OPENID_OFFER_CREATE_FAIL | An offer that was never created, for example one referencing an unknown credential configuration |
OPENID_AUTHORIZE_FAIL | A rejected authorization request, including a malformed request and a resumed interaction that could no longer be found |
OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_FAIL | A holder who came back from your authentication provider but could not be admitted |
OPENID_INTERACTION_HOOK_FAIL | Your interaction hook rejecting the holder, or returning a response MATTR VII could not accept |
OPENID_TOKEN_FAIL | A rejected token exchange, most often a reused, expired, or mismatched authorization code |
OPENID_CREDENTIAL_FAIL | A rejected credential request, including claim mapping problems |
OPENID_CLAIM_SOURCE_RETRIEVE_FAIL | Your claims source being unreachable or returning something unusable |
WEBHOOK_EVENT_PROCESS_FAIL | A webhook that MATTR VII could not deliver |
Identifiers in this flow
The identifiers common to every flow
apply here. This flow adds the following, all of which live inside the event data and therefore
cannot be filtered on directly:
| Identifier | What it identifies | Where you first get it |
|---|---|---|
interactionId | One interaction inside the authorization phase, either the login or the interaction hook | OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_START, or the state in the session token sent to your hook |
userId | The MATTR VII user record for the holder | OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_SUCCESS, or a user search |
credentialId | The issued credential | USER_CREDENTIAL_CREATE_SUCCESS, or a webhook |
credentialConfigurationId | The configuration the credential was issued against | Your credential configuration |
claimSourceId | The claims source MATTR VII called | Your claims source configuration |
A single issuance spans a dozen separate exchanges, so it has a dozen different request IDs. The
sessionId ties together everything from the completion of authentication onwards.
Using sessionId
The sessionId is the identifier to group a journey on, but it is not on every event and the
earliest events can report a different value:
OPENID_AUTHORIZE_STARTcarries nosessionIdat all. Itsdataholds the authorization request parameters, so the only handles it gives you areclient_id,scope,redirect_uri,state, and the PKCEcode_challenge.OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_STARTis emitted as the interaction begins, before the request is handled, so itssessionIdcan be the value MATTR VII read from the holder's existing session cookie rather than the session this journey goes on to use. Treat it as a marker for the step rather than a key to group on.- From
OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_SUCCESSonwards the value is the session the journey actually uses.OPENID_AUTHORIZE_SUCCESSand every token and credential event carry it, so you can rely on it from that point.
Use the interactionId to group the authorization phase itself, and use OPENID_AUTHORIZE_SUCCESS
to cross from one to the other.
Finding the attempt for one holder
Unlike the Pre-authorized Code flow, an Authorization Code offer is not tied to a holder. The offer is
reusable, it is shared with many people, and OPENID_OFFER_CREATE_SUCCESS records only the URI that
was generated. There is no offer identifier to search on, so knowing which offer a holder used tells
you nothing about their individual attempt.
Start from the user record instead. MATTR VII allocates a userId from the holder's subjectId at
your authentication provider, as described in
User creation by issuance workflow. Take that
subjectId, search for the user by
authenticationProvider.subjectId, and then match the returned id against data.userId in the
events over the window the holder gave you.
If no user record exists at all, the holder never completed authentication, and the attempt ended in step 5.
Filtering by clientIds will not narrow this down for you. The authorization, token, and credential
endpoints are called by the wallet, and their events carry no top-level client ID, so a clientIds
filter returns your offer creation events and nothing that followed them. The wallet's OAuth client is
still recorded as data.client_id on OPENID_AUTHORIZE_START and OPENID_TOKEN_START, and as
data.clientId on OPENID_CREDENTIAL_START and OPENID_CREDENTIAL_SUCCESS.
Confirming issuance without polling
Rather than polling the Events API to find out whether a credential was issued, subscribe to a
webhook. The OpenIdCredentialIssued and
OpenIdCredentialIssuedSummary event types both cover this flow, and their payload carries
credentialId, userId, credentialConfigurationId, format, and clientId.
There is no webhook for a failed issuance. Webhooks tell you that issuance succeeded. They cannot tell you that it failed, and in this flow most abandonment happens well before the credential endpoint is ever reached.
Next steps
How would you rate this page?
Last updated on