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.
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 transaction data entry's credential_ids may list more than one
eligible credential query — this is a choice of one of, not all of:
the wallet selects a single credential satisfying one of the listed
queries and binds the approval to that credential alone.
A proof request may also include more than one transaction data entry, each requiring its own approval. In that case, no credential may be used to satisfy more than one entry, even when the same credential query is listed as eligible for both.
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. Whether this is present depends on thetransactionDataProviderconfiguration: it's populated only if the provider is configured withtransactionDataDisplayParams(see Transaction data providers forgroupPath/titlePath/attributesconfiguration). It's optional from the wallet's perspective, but providing it 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.Set it explicitly whenever more than one transaction data entry could be satisfied by the same credential — since one credential can never bind to two entries, an unambiguous assignment is required. For example, given two entries that both list
identity_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.
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.