Create a Credential Offer
This guide walks you through creating a credential offer from an existing profile. The offer generates an OID4VCI credential offer URL that can be claimed by any compliant wallet.
Prerequisites
Before creating an offer, ensure you have:
- Issuer2 API running
- Credential Profile configured — or use one of the ready-made default profiles
Create an Offer
Endpoint: POST /issuer2/credential-offers | API Reference
Example Request
curl -X 'POST' \
'http://localhost:7002/issuer2/credential-offers' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED"
}'
Body Parameters
- profileId: String (required) - The ID of the credential profile to use.
- authMethod: String (required) - Authentication method:
PRE_AUTHORIZEDorAUTHORIZED. - valueMode: String (optional) - How the credential offer is delivered (default:
BY_REFERENCE).BY_REFERENCE— offer URL contains a reference, wallet fetches the full offer.BY_VALUE— full credential offer embedded in the URL. - issuerStateMode: String (optional) - Whether to include issuer state (default:
OMIT).OMIT— no issuer state.INCLUDE— include issuer state for session correlation with authorization servers. - expiresInSeconds: Integer (optional) - Offer validity duration in seconds (default: 300). Set to
-1for no expiration. - txCode: Object (optional) - Transaction code (PIN) configuration for pre-authorized flow. See Transaction Code Configuration.
- txCodeValue: String (optional) - Specific PIN value (if not provided, one is generated).
- runtimeOverrides: Object (optional) - Override profile values for this offer. See Runtime Overrides.
- sessionId: String (optional) - Session ID for the offer. If not provided, a random session ID will be generated.
Example Response
{
"offerId": "abc123-def456-ghi789",
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"expiresAt": 1704067500000,
"credentialOffer": "openid-credential-offer://?credential_offer_uri=http%3A%2F%2Flocalhost%3A7002%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789"
}
Response Fields
- offerId: String - Unique identifier for this offer.
- profileId: String - The profile used to create this offer.
- authMethod: String - The authentication method.
- issuerStateMode: String - The issuer state mode (for authorized flow).
- expiresAt: Long - Unix timestamp (ms) when the offer expires.
- txCodeValue: String - The PIN value (only for pre-authorized flow with txCode).
- credentialOffer: String - The OID4VCI credential offer URL.
Examples
Pre-Authorized Flow with PIN
Require a PIN for additional security:
curl -X POST 'http://localhost:7002/issuer2/credential-offers' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"txCode": {
"input_mode": "numeric",
"length": 6,
"description": "Please enter the PIN sent to your email"
}
}'
Response includes the PIN:
{
"offerId": "abc123-def456-ghi789",
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"expiresAt": 1704067500000,
"txCodeValue": "123456",
"credentialOffer": "openid-credential-offer://..."
}
Pre-Authorized Flow with Custom PIN
Specify your own PIN value:
curl -X POST 'http://localhost:7002/issuer2/credential-offers' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"txCode": {
"input_mode": "numeric",
"length": 4
},
"txCodeValue": "1234"
}'
Authorization Code Flow
For user authentication via external IdP:
curl -X POST 'http://localhost:7002/issuer2/credential-offers' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "identityCredentialSdJwt",
"authMethod": "AUTHORIZED",
"issuerStateMode": "INCLUDE"
}'
issuerStateMode: "INCLUDE" lets the Issuer2 API correlate the authorization request it receives back with this offer's issuance session. See Authorization Code Flow for the full sequence, including how (and when) issuer_state is forwarded to the external authorization server.
You can run this example without setting up an identity provider. Out of the box it uses a hosted walt.id test provider — open the offer in a wallet and log in as jane@walt.id / jane. Because the identityCredentialSdJwt profile maps given_name and family_name from the ID token, the issued credential is populated with Jane's name from her login.
Offer by Value
Embed the full offer in the URL (useful for offline scenarios):
curl -X POST 'http://localhost:7002/issuer2/credential-offers' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"valueMode": "BY_VALUE"
}'
Custom Expiration
Set a longer expiration time:
curl -X POST 'http://localhost:7002/issuer2/credential-offers' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"expiresInSeconds": 3600
}'
Runtime Overrides
Override profile values for a specific offer — including the signing key, not just the credential data:
curl -X POST 'http://localhost:7002/issuer2/credential-offers' \
-H 'Content-Type: application/json' \
-d '{
"profileId": "universityDegree",
"authMethod": "PRE_AUTHORIZED",
"runtimeOverrides": {
"issuerKey": {
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "5bx6BJCRTtKmXacRtnhjQ6i07UWaAWRixZvFD3t9f00",
"crv": "Ed25519",
"kid": "N4dxEvjiNg9RwEqySdHm2VTjfB0M0JyO9UV5_SS9sQE",
"x": "u2fhaRWUv-Uo24he12WLHTbcdJHWis7VCBA8PJkB0o0"
}
},
"issuerDid": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5Iiwia2lkIjoiTjRkeEV2amlOZzlSd0VxeVNkSG0yVlRqZkIwTTBKeU85VVY1X1NTOXNRRSIsIngiOiJ1MmZoYVJXVXYtVW8yNGhlMTJXTEhUYmNkSkhXaXM3VkNCQThQSmtCMG8wIn0",
"credentialData": {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"type": ["VerifiableCredential", "UniversityDegree"],
"credentialSubject": {
"degree": {
"type": "MasterDegree",
"name": "Master of Computer Science"
}
}
},
"notifications": {
"webhook": {
"url": "https://my-server.com/webhook/special-offer"
}
}
}
}'
issuerKey (and issuerDid/x5Chain) override the profile's default signing key for this offer only — useful for issuing a one-off credential with a different key without touching the profile itself. Provide both together, since a mismatched key/DID pair will fail to sign.
Available Override Fields
| Field | Description |
|---|---|
issuerDid | Override the issuer DID |
issuerKey | Override the signing key |
x5Chain | Override the X.509 certificate chain |
credentialData | Override the credential data |
mapping | Override the data mapping |
selectiveDisclosure | Override the SD-JWT selective disclosure configuration |
idTokenClaimsMapping | Override ID token claims mapping |
mDocNameSpacesDataMappingConfig | Override mDoc data mapping |
credentialStatus | Override credential status configuration |
notifications | Override notification settings |
Transaction Code Configuration
The txCode object configures PIN requirements:
| Property | Type | Description |
|---|---|---|
input_mode | String | Input type: numeric or text |
length | Integer | Length of the PIN |
description | String | Description shown to the user |
Using the Credential Offer URL
The credentialOffer URL can be:
- Displayed as QR Code – User scans with their wallet app
- Sent as Deep Link – User clicks link on mobile device
- Embedded in Email/Message – User clicks to open in wallet
Example Decoded Offer URL
openid-credential-offer://?credential_offer_uri=http://localhost:7002/openid4vci/credential-offer?id=abc123-def456-ghi789
Next Steps
- Notifications – Configure webhook notifications
- Data Functions – Populate credentials with dynamic data
