How to troubleshoot the OID4VCI Pre-authorized Code flow
Work from a symptom to the analytic event that explains it, using the identifiers you already have.
This guide covers symptoms specific to the OID4VCI Pre-authorized Code flow. It assumes you know which events the flow emits, as described in Monitoring the OID4VCI Pre-authorized Code flow.
For the general method, which covers locating events by request ID, reading data.error, and what to
send our support team, refer to
How to monitor a flow.
An issuance spans three separate exchanges, so the last event that fired is usually enough to tell you which one broke. Some requests are also rejected before MATTR VII records them, so the absence of an event does not mean the attempt never happened. Refer to when there is no event.
The holder says the offer no longer works
What you will find: OPENID_PRE_AUTHORIZED_OFFER_CREATE_SUCCESS for the offer, and no
OPENID_TOKEN_START carrying that offerId.
Three different causes produce this same signature:
- The offer expired. By default an offer expires 5 minutes after creation, and the maximum you
can configure is 10 minutes. Compare
data.expiresAtonOPENID_PRE_AUTHORIZED_OFFER_CREATE_SUCCESSagainst when the holder says they tried. - The offer was already claimed. Pre-authorized codes are single use. Once a credential has been
issued the code is consumed, and every later attempt fails, including by the same holder. Refer to
single-use offers. Look
for a
USER_CREDENTIAL_CREATE_SUCCESScarrying the sameofferId. If you find one, the offer was claimed successfully and the holder is retrying an old offer. - The offer was invalidated by too many wrong transaction codes. Covered below.
The fix: create a new offer. Offers cannot be extended or re-armed.
If holders hit this often, the usual cause is the gap between creating the offer and the holder opening it. Create the offer at the moment the holder is ready to claim it rather than in advance, and consider configuring a longer expiry.
The holder entered a transaction code and it was rejected
What you will find: OPENID_TOKEN_FAIL.
The behavior changes with the number of attempts:
| Attempt | error.type | Effect |
|---|---|---|
| 1 and 2 | invalid_tx_code | The offer remains claimable |
| 3 | invalid_grant | The offer is invalidated |
| 4 and later | No event | The offer no longer exists |
After three wrong attempts the offer is permanently invalidated, as described in accepting a credential offer. Every attempt after that is indistinguishable from an unknown code.
You will also see OPENID_TOKEN_FAIL with an error.type of invalid_request when a transaction
code was required but not supplied, or supplied when the offer did not require one.
The fix: create a new offer and deliver the transaction code again. If this happens frequently, check how you are communicating the code to holders.
The wallet obtained an access token but the credential request failed
What you will find: OPENID_TOKEN_SUCCESS, then nothing.
The credential request was rejected before issuance began. Two causes account for most cases:
- The access token expired. Compare the
data.expires_invalue onOPENID_TOKEN_SUCCESS, and the event timestamp, against when the holder says the wallet requested the credential. - The token scope does not cover the requested credential.
The scope case is worth understanding because it fails late and quietly. If no credential
configuration matches the offer, MATTR VII still issues an access token, and it issues one with
no scope. The token exchange therefore looks entirely successful. The problem only surfaces at
the credential endpoint. If you see an OPENID_TOKEN_SUCCESS whose data.scope is absent or
empty, the offer referenced a credential configuration that MATTR VII could not resolve.
The fix: for expiry, have the wallet complete the exchange promptly. For scope, check that the
credentials array in your offer request references a
credential configuration that exists and is an
mDoc configuration.
Your claims source did not respond usably
What you will find: OPENID_CLAIM_SOURCE_RETRIEVE_START, then
OPENID_CLAIM_SOURCE_RETRIEVE_FAIL.
The error.type distinguishes MATTR VII being unable to reach your claims source at all, your claims
source responding with a non-success status, and your claims source responding with something that is
not a JSON object. The event also carries claimSourceId, and
OPENID_CLAIM_SOURCE_RETRIEVE_SUCCESS carries claimSource.url and claimSource.requestMethod, so
you can confirm which endpoint MATTR VII called.
A claims source failure returns a server error to the wallet rather than a rejection. If the holder reports a server error rather than being turned away, the claims source is the first place to look.
The outbound call to your claims source carries the same x-request-id as the credential request
that triggered it. If you log that header on your claims source, you can match your own server logs
to the MATTR VII events directly.
The fix: check your claims source availability and its response format. If it uses OAuth, look
also for OPENID_CLAIM_SOURCE_AUTHORIZATION_FAIL.
Claims came back but the credential could not be built
What you will find: OPENID_CREDENTIAL_FAIL, with a message naming a claim that could not be
resolved.
This means the claims your source returned do not line up with what your credential configuration expects. The usual causes are a required claim that is absent from the source response, a claim that resolved to a different data type than the element expects, and a validity claim that is not an ISO 8601 string.
The fix: compare the claims your source returns against the claimMappings in your
credential configuration.
A certificate or signing problem
What you will find: OPENID_CREDENTIAL_FAIL reporting a general failure to create the mobile
credential, and MOBILE_CREDENTIAL_SIGN_FAIL on the same request ID.
The useful error is in MOBILE_CREDENTIAL_SIGN_FAIL, not in OPENID_CREDENTIAL_FAIL, so always
check for one on the same request ID before concluding that the cause is unknown. Its error.type
and error.message name the specific problem. These fall into two groups: the certificate MATTR VII
needed was missing, expired, inactive, or not found, or the credential validity period you asked for
could not be satisfied, for example because it would outlive the document signer or falls outside the
validity of any active IACA.
The fix: most of these point at certificate management. Check that your IACA is active and that its validity window covers the credential you are issuing.
The credential was issued but the webhook never arrived
What you will find: OPENID_CREDENTIAL_SUCCESS, and then either WEBHOOK_EVENT_PROCESS_FAIL or
no webhook events at all.
WEBHOOK_EVENT_PROCESS_FAILmeans MATTR VII tried and your endpoint rejected the delivery or was unreachable. Retries appear as furtherWEBHOOK_EVENT_PROCESS_STARTandWEBHOOK_EVENT_PROCESS_FAILpairs rather than as a retry count on a single event.- No webhook events at all means the webhook was skipped. A disabled or deleted webhook produces no event whatsoever, so confirm the webhook still exists and is enabled.
Refer to Webhooks for delivery behavior and signature validation.
Nothing appears for the attempt
Work through these in order:
- Check the step. The
.well-knownmetadata fetch and the wallet reading the offer produce no events at all. Refer to what you can observe. - Check the credential format. Pre-authorized Code offers support mDocs only.
- Check the time window and the tenant. Confirm you are querying the tenant that issued the offer, and that your window is in UTC.
- Check whether the request could have been rejected before it was recorded. Refer to when there is no event.
Next steps
How would you rate this page?
Last updated on