Skip to main content

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:

  • transactionDataTypes lists which types it can process.
  • formats lists 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

info

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:

  • type must reference a configured transactionDataProvider.
  • The transaction data object itself must be well-formed according to the specification implemented by that provider — for QES_APPROVAL, this includes confirming hashAlgorithmOID refers to a supported algorithm and that either credentialID or signatureQualifier is 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_ids must match a credential query ID in the accompanying DCQL query.
  • Each of those referenced credential queries must have require_cryptographic_holder_binding set to true; 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

info

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 as transactionDataId in 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

info

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 the transactionDataProvider configuration: it's populated only if the provider is configured with transactionDataDisplayParams (see Transaction data providers for groupPath/titlePath/attributes configuration). 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

info

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 the id from 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_card and residence_permit as 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.