Transaction Authorization
Some proof requests ask the holder to authorize a specific transaction —
not just prove possession of a credential — before presenting it. This
authorization is carried as transaction_data in the OpenID4VP request,
and represents additional information the holder should review and
approve before sharing anything.
Overview
When a request includes transaction data, Procivis One surfaces it to the holder for review before any credential is shared. If the holder approves, that approval is cryptographically bound to the credential presentation, so the verifier can confirm both that a valid credential was presented and that the holder reviewed and approved this exact transaction. See Requesting Authorization for Transactions for the verifier side of this flow.
This document describes what Procivis One does with transaction data end-to-end, including rendering it in the Business (server) and mobile wallets. If you are building your own wallet against Core's APIs, the same sequence applies.
Prerequisites
At least one instance of transactionDataProvider must be configured. See
Configuring OpenID4VP - Transaction data
for how to configure one.
To see what is supported, request your configuration
and check each instance's capabilities:
transactionDataTypeslists which types it can process.formatslists which credential formats are supported.featureslists behavioral capabilities.SUPPORTS_MULTIPLE_TX_DATA_PER_PRESENTATION: if present, a single credential presentation from this provider can authorize more than one transaction data entry at once. If absent (as withQES_APPROVAL), each entry this provider handles needs a credential of its own — see Submitting approval for what that means in practice.
When processing incoming requests (via handle-invitation), the wallet
checks the request against capabilities of the providers and automatically
uses the provider that supports the request.
Receiving transaction data
REST: POST /api/interaction/v1/handle-invitation
Mobile: handleInvitation(request)
When an invitation contains a transaction_data query parameter,
Procivis One decodes it and stores each entry as part of the
interaction. It expects type and credential_ids;
transaction_data_hashes_alg is optional.
At this point, Procivis One validates the request and fails the invitation immediately if any of the following don't hold:
typemust reference a configuredtransactionDataProvider.- The transaction data object itself must be well-formed according to
the specification implemented by that provider — for
QES_APPROVAL, this includes confirminghashAlgorithmOIDrefers to a supported algorithm and that eithercredentialIDorsignatureQualifieris present. This is the same validation the verifier's provider runs when the request is created — see What happens after the request is created. - Every ID in a transaction data entry's
credential_idsmust match a credential query ID in the accompanying DCQL query. - Each of those referenced credential queries must have
require_cryptographic_holder_bindingset totrue; transaction data can only be requested against credentials capable of a holder-binding proof.
A proof request may also include more than one transaction data entry,
each requiring its own approval. Whether one credential can satisfy
more than one entry depends on the transaction data type's features:
providers with SUPPORTS_MULTIPLE_TX_DATA_PER_PRESENTATION (the SCA
types) can combine several entries under one credential; providers
without it (QES_APPROVAL) require a distinct credential per entry,
even when the same credential query is listed as eligible for more
than one.
Discovering what's requested
REST: GET /api/proof-request/v2/{id}/presentation-definition
Mobile: getPresentationDefinitionV2(proofId)
Alongside the usual credential queries, the presentation definition includes a summary of any transaction data attached to the request:
"transactionData": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "QES_APPROVAL",
"credentialQueryIds": ["identity_card"]
}
]
id— identifies this transaction data entry. Use it astransactionDataIdin the next call.type— the transaction data provider name.credentialQueryIds— which of the request's credential queries this entry applies to.
Fetching full transaction data detail
REST: GET /api/proof-request/v1/{proofId}/transaction-data/{transactionDataId}
Mobile: holderGetTransactionData(proofId, transactionDataId)
Returns the full detail for a single transaction data entry, including data formatted for display:
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "QES_APPROVAL",
"credentialQueryIds": ["identity_card"],
"transactionDataDisplay": [
{
"title": "Example Contract",
"attributes": [
{ "key": "transactionData.qesApproval.documentInfo.href", "value": "https://..." }
]
}
],
"rawTransactionData": { "...": "..." }
}
transactionDataDisplay— grouped, human-readable key-value data for presenting the transaction to the holder. Populated based on the provider'stransactionDataDisplayParamsconfiguration; if a given payload doesn't match the configuredgroupPath, the array is empty rather than absent. Providing this is what lets a wallet UI render the transaction without needing to understand each type's raw payload.rawTransactionData— the full, provider-specific transaction data as received from the verifier, useful for a "details" or "advanced" view. On mobile, this is returned as a JSON-encoded string rather than a native object — over REST it's a JSON object embedded directly.
Submitting approval
REST: POST /api/interaction/v2/presentation-submit
Mobile: holderSubmitProofV2(interactionId, submission)
{
"interactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"submission": {
"identity_card": [
{
"credentialId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userSelections": [],
"transactionDataIds": []
}
]
}
}
submission is keyed by credential query id, each mapping to the
credential(s) submitted for that query.
-
userSelections— unrelated to transaction data; this is where the holder opts in to sharing optional claims. -
transactionDataIds— which transaction data entries (by theidfrom the previous step) this credential's presentation should bind to. In most cases you can omit this or pass an empty array: an entry not explicitly listed anywhere is auto-assigned to an applicable credential automatically.Whether you need to set it explicitly depends on the transaction data type, mirroring the distinction described under Receiving transaction data:
Types that require a distinct credential per entry (e.g.
QES_APPROVAL) need an explicit, unambiguous assignment whenever more than one entry could be satisfied by the same credential. Given two entries that both listidentity_cardandresidence_permitas eligible:
{
"interactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"submission": {
"identity_card": [
{
"credentialId": "cred-1",
"transactionDataIds": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"]
}
],
"residence_permit": [
{
"credentialId": "cred-2",
"transactionDataIds": ["aaaaaaa4-5717-4562-b3fc-2c963f66afa6"]
}
]
}
}
Either credential could technically satisfy either entry here — the explicit assignment is what determines which credential is understood to have approved which transaction.
Types that support combining several entries under one credential
let a single presentation authorize more than one entry at once —
list every entry's id under the one credential handling them:
{
"interactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"submission": {
"identity_card": [
{
"credentialId": "cred-1",
"transactionDataIds": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6",
"aaaaaaa4-5717-4562-b3fc-2c963f66afa6"
]
}
]
}
}
After submission
Once the presentation is submitted, the wallet's role in this exchange is complete. The verifier is responsible for validating the presentation and for anything that follows from the transaction being approved. See Requesting Authorization for Transactions for Procivis One's implementation of that side of the flow.