Use transaction codes
This guide shows you how to add transaction code (tx_code) authorization to your credential issuance flows with the Truvity EUDIW Connector. A transaction code is a one-time code that the user must enter in their wallet before the credential is issued, providing an additional layer of authorization beyond the pre-authorized code.
- A running connector instance with its public base URL configured and a callback URL pointing to your backend endpoint (for example,
http://localhost:3000/callback/issuance). See connector architecture for the deployment model and how callbacks are delivered. - Access to the internal management API (port 8081)
- A callback endpoint reachable from the connector over internal networking
- An X.509 access certificate configured in the connector
- An Issuer Signing Certificate configured in the connector. See Manage certificates to generate one.
- Type Metadata configured for the target credential type
- Familiarity with the credential issuance how-to
Overview
Transaction codes add an additional authorization layer to credential issuance. When you include a tx_code configuration in your offer request, the connector generates a one-time code that you deliver to the user through a separate channel (for example, SMS or email). The user enters this code in their wallet, and the authorization server validates it at the token endpoint before issuing an access token as part of the pre-authorized code flow.
Use transaction codes when:
- Issuing high-value credentials that require additional user verification
- Regulatory requirements mandate out-of-band authorization
- You want to confirm the user requesting the credential controls a specific phone number or email address
Time to implement: 30 minutes.
Step 1: Create an offer with tx_code
Call POST /offers on the management API with a tx_code object in the request body. The tx_code object specifies the code format (input_mode) and length.
- cURL
curl -X POST http://connector:8081/offers \
-H "Content-Type: application/json" \
-d '{
"credential_configuration_id": "AccountOwnershipCredential",
"claims": {
"bankName": "Example Bank",
"accountHolder": "Erika Mustermann",
"iban": "DE99370501981234567890"
},
"tx_code": {
"input_mode": "numeric",
"length": 6,
"description": "Enter the code sent to your phone"
}
}'
Example response:
{
"offer_id": "abc123def456",
"credential_offer_uri": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.example.com%2Foidc4vci%2Foffers%2Fabc123def456",
"tx_code_value": "679042"
}
The request body includes the same credential_configuration_id and claims fields as a standard credential offer, plus:
tx_code.input_mode—the type of code the user enters. Supported values:"numeric"(digits only) or"text"(any characters).tx_code.length—the number of characters in the code (for example,6).tx_code.description(optional)—a human-readable description of the transaction code purpose that wallets can display to the user.
The response includes the standard offer_id and credential_offer_uri fields, plus:
tx_code_value—the generated transaction code. Store this value and deliver it to the user through a separate channel. This field is only present whentx_codeis included in the request.
Step 2: Deliver the tx_code to the user
Send the tx_code_value to the user through a channel separate from the credential offer (for example, SMS or email). The user enters this code in their wallet when prompted during the issuance flow.
Extract the tx_code_value from the offer response and deliver it to the user through your chosen channel. Your implementation should:
- Retrieve the
tx_code_valuestring from thePOST /offersresponse. - Send it to the user via SMS, email, or in-app notification before the session expires (default five minutes).
- Store the
offer_idfor callback correlation.
For example, call your SMS gateway or email service with the code value immediately after creating the offer.
Delivering the transaction code is your responsibility. The connector generates the code and returns it in the offer response, but does not send it to the user. Choose a delivery channel appropriate for your use case:
- SMS—suitable for mobile-first flows where the user's phone number is verified.
- email—suitable when the user's email address is verified.
- In-app notification—suitable when the user is authenticated in your app.
The wallet prompts the user to enter the transaction code before exchanging the pre-authorized code for an access token. The authorization server validates the code at the token endpoint. If the code is incorrect or expired, the authorization server rejects the token request with an invalid_grant error, and the wallet cannot proceed with the issuance flow.
Step 3: Handle the callback
The callback payload for tx_code flows uses the same structure as standard issuance callbacks. Handle all three statuses relevant to tx_code flows: OFFER_CREATED, ISSUED, and EXPIRED.
- cURL
# Simulate an ISSUED callback (tx_code was correct, credential issued)
curl -X POST http://backend.example.com:3000/callback/issuance \
-H "Content-Type: application/json" \
-d '{
"eventId": "abc123def456",
"status": "ISSUED",
"offerId": "abc123def456"
}'
# Simulate an EXPIRED callback (session expired — for example, user never entered the tx_code)
curl -X POST http://backend.example.com:3000/callback/issuance \
-H "Content-Type: application/json" \
-d '{
"eventId": "abc123def456",
"status": "EXPIRED",
"offerId": "abc123def456"
}'
When the user enters an incorrect transaction code, the authorization server rejects the token request with invalid_grant. The wallet cannot exchange the pre-authorized code for an access token, so the issuance flow stalls. The connector has no visibility into this failure because the wallet never reaches the credential endpoint. The session eventually expires, and the connector delivers an EXPIRED callback. Create a new offer with a fresh transaction code if the user wants to retry.
For the complete list of callback payload fields and status descriptions, see the callback events reference.
Testing
Test checklist
- Offer with
tx_codereturnstx_code_valuein the response - Correct transaction code: issuance completes and callback receives
ISSUEDstatus - Incorrect transaction code: wallet receives
invalid_grantfrom the authorization server and cannot proceed; session eventually expires and callback receivesEXPIREDstatus - Expired session (user never entered the code): callback receives
EXPIREDstatus - Transaction code is delivered to the user through the chosen channel
- Retry flow creates a new offer with a fresh transaction code
Troubleshooting
invalid_grant error from the authorization server
The authorization server returns invalid_grant when the transaction code is incorrect or has already been used. This error is returned to the wallet at the token endpoint—the connector does not receive a direct notification of this failure. The session remains in OFFER_CREATED state until it expires, at which point the connector sends an EXPIRED callback. Verify that:
- The
tx_code_valuefrom the offer response is delivered to the user without modification. - The user enters the code before the session expires (default TTL is five minutes).
- The code has not already been used in a previous token exchange attempt.
Create a new offer with a fresh transaction code if the user needs to retry.
tx_code_value not delivered to the user
If the user does not receive the transaction code, the issuance flow stalls until the session expires. Check your delivery channel (SMS gateway, email service) for failures. The connector doesn't retry code delivery—this is your responsibility.
Transaction code expired before the wallet completes the flow
The transaction code is tied to the session TTL (default five minutes). If users consistently time out, consider guiding them to enter the code promptly after receiving it, or adjust the session TTL if your deployment supports it.
Next steps
- Issue a credential—generic credential issuance without transaction codes
- Build an AOC issuance flow—end-to-end tutorial including optional tx_code
Further reading
- OID4VCI protocol—how the issuance protocol works
- Error codes—wallet-facing HTTP error responses
- Callback events—issuance event statuses and payload fields