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 any compliant wallet can claim.
Prerequisites
Before creating an offer, ensure you have:
- An Issuer2 service — A running issuer service. See Setup.
- A credential profile — An existing profile to issue from. See Create a Profile.
Create an Offer
The offer is created against the profile as the target — its full resource path, {organizationID}.{tenantID}.{issuerServiceID}.{profileId}.
Endpoint: POST /v2/{profileTarget}/issuer-service-api/credentials/offers | API Reference
Example Request
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"authMethod": "PRE_AUTHORIZED",
"valueMode": "BY_REFERENCE",
"expiresInSeconds": 300
}'
Path Parameters
- orgID: String (required) - Your organization ID, e.g.
test.enterprise-sandbox.waltid.dev. - profileTarget: String (required) - The resource identifier of the credential profile,
{organizationID}.{tenantID}.{issuerServiceID}.{profileId}, e.g.waltid.tenant1.issuer1.profile-abc123.
Header Parameters
- Authorization: String (required) - Bearer token. Format:
Bearer {token}.
Body Parameters
- authMethod: String (required) - The OID4VCI flow used to claim the offer. Options:
"PRE_AUTHORIZED"— Pre-authorized code flow (no user authentication)."AUTHORIZED"— Authorization code flow (user authentication required).
- valueMode: String (optional) - How the offer is delivered (default:
BY_REFERENCE). Options:"BY_REFERENCE"— Offer URL contains a reference; the wallet fetches the full offer."BY_VALUE"— Full credential offer embedded in the URL.
- issuerStateMode: String (optional) - Whether to include issuer state. Valid only for
AUTHORIZEDoffers; omit it forPRE_AUTHORIZED. Options:"INCLUDE"— Include issuer state for session correlation with the authorization server."OMIT"— No issuer state.
- expiresInSeconds: Integer (optional) - Offer / issuance session validity duration in seconds (default: 300). Set to
-1for no expiration and no MongoDB TTL auto-ejection. Finite values delete the stored session after expiry. See Session Data Ejection. - txCode: Object (optional) - Transaction code (PIN) configuration for the pre-authorized flow. See Transaction Code (PIN).
- txCodeValue: String (optional) - A specific PIN value. If omitted, one is generated.
- runtimeOverrides: Object (optional) - Override profile values for this offer only. See Runtime Overrides.
- sessionId: String (optional) - Session ID for the issuance session. If omitted, a random UUID is generated.
Example Response
{
"offerId": "abc123-def456-ghi789",
"profileId": "profile-abc123",
"profileVersion": 1,
"authMethod": "PRE_AUTHORIZED",
"issuerStateMode": null,
"expiresAt": 1704067500000,
"credentialOffer": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fwaltid.enterprise-sandbox.waltid.dev%2Fv2%2Fwaltid.tenant1.issuer1%2Fissuer-service-api%2Fopenid4vci%2Fcredential-offer%3Fid%3Dabc123-def456-ghi789"
}
Response Fields
- offerId: String - Unique identifier for this offer (also the issuance session ID).
- profileId: String - The profile used to create this offer.
- profileVersion: Integer - The version of the profile used.
- authMethod: String - The authentication method.
- issuerStateMode: String - The issuer state mode (present only for
AUTHORIZEDoffers). - 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.
Response Codes
201— Offer created successfully.400— Invalid request body.401— Invalid or missing authentication token.
🎉 You've created a credential offer. Hand the credentialOffer URL to a wallet as a QR code or deep link to claim it.
Transaction Code (PIN)
For the pre-authorized flow, you can require a PIN for additional security.
{
"authMethod": "PRE_AUTHORIZED",
"txCode": {
"input_mode": "numeric",
"length": 6,
"description": "Please enter the PIN sent to your email"
}
}
txCode Parameters:
- input_mode: String (optional) - Input type:
numericortext(default:numeric). - length: Integer (optional) - Length of the PIN.
- description: String (optional) - Description shown to the user.
To set the PIN value yourself, provide txCodeValue:
{
"authMethod": "PRE_AUTHORIZED",
"txCode": {
"input_mode": "numeric",
"length": 6
},
"txCodeValue": "123456"
}
Runtime Overrides
You can override any profile value for a specific offer using runtimeOverrides:
{
"authMethod": "PRE_AUTHORIZED",
"runtimeOverrides": {
"issuerDid": "did:key:z6MkNewDid...",
"issuerKeyId": "waltid.tenant1.kms1.differentKey",
"subjectId": "did:key:z6MkSubjectDid...",
"w3cVersion": "W3CV2",
"credentialData": {
"@context": ["https://www.w3.org/ns/credentials/v2"],
"type": ["VerifiableCredential", "CustomCredential"],
"credentialSubject": { "customField": "customValue" }
},
"mapping": { "id": "<uuid>" },
"credentialStatus": {
"statusCredentialConfig": "waltid.tenant1.credentialstatus.config2",
"initialStatus": "0x0"
},
"notifications": {
"webhook": { "url": "https://different-webhook.com/callback" }
}
}
}
Available Override Fields
| Field | Description |
|---|---|
issuerDid | Override the issuer DID. |
issuerKeyId | Override the signing key. |
x5Chain | Override the X.509 certificate chain. |
subjectId | Set the credential subject ID. |
credentialData | Override the credential data. |
mapping | Override the data mapping. |
selectiveDisclosure | Override the SD-JWT selective disclosure configuration. |
idTokenClaimsMapping | Override the ID token claims mapping. |
mDocNameSpacesDataMappingConfig | Override the mDoc data mapping. |
authorizedTransactionDataTypes | Override mDoc OpenID4VP transaction-data types authorized in the MSO KeyAuthorizations. |
credentialStatus | Override the credential status configuration. |
notifications | Override the notification settings. |
w3cVersion | Override the W3C data model version. |
loaAchieved | Override the Level of Assurance achieved (HIGH, SUBSTANTIAL, LOW). Defaults to NON_APPLICABLE. Appened to the Audit Log. |
identityProofingRefId | Provide a reference ID for external identity proofing or other level of assurance requirements. Appened to the Audit Log. e.g. 1234567890 |
issuer_state correlates a credential offer with the later authorization request in the authorization code flow. The issuer creates it, the wallet treats it as opaque, and if present in the offer the wallet sends it back in the authorization request. AUTHORIZED offers omit issuer_state unless issuerStateMode is INCLUDE (the default is OMIT). Because offer-specific runtimeOverrides are correlated to the issuance session via issuer_state, set issuerStateMode to INCLUDE whenever an AUTHORIZED offer uses runtimeOverrides — otherwise, with no issuer_state, the authorization request cannot be matched back to this offer's overrides. Note that the create request itself is not rejected if you combine runtimeOverrides with OMIT (it returns 201); the overrides simply cannot take effect.
Examples
Pre-Authorized Flow with PIN
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"authMethod": "PRE_AUTHORIZED",
"txCode": {
"input_mode": "numeric",
"length": 4
},
"expiresInSeconds": 600
}'
Authorization Code Flow
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"authMethod": "AUTHORIZED",
"issuerStateMode": "INCLUDE",
"expiresInSeconds": 900
}'
The same issuer_state from the offer is forwarded to the authorization server in the authorization request, e.g.:
https://keycloak.demo.walt.id/realms/myrealm/protocol/openid-connect/auth?client_id=issuer_api&redirect_uri=.../issuer-service-api/openid4vci/callback&scope=openid_profile&state=74b5206d7a15c9ec&response_type=code&issuer_state=cadbeaf4-d412-4ce3-b8d4-e8d5c70d4a3f
See Authorization Code Flow for the full sequence.
Offer with Custom Credential Data
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"authMethod": "PRE_AUTHORIZED",
"runtimeOverrides": {
"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" }
}
}
}
}'
Offer by Value
curl -X 'POST' \
'https://{orgID}.enterprise-sandbox.waltid.dev/v2/{profileTarget}/issuer-service-api/credentials/offers' \
-H 'accept: application/json' \
-H 'Authorization: Bearer {yourToken}' \
-H 'Content-Type: application/json' \
-d '{
"authMethod": "PRE_AUTHORIZED",
"valueMode": "BY_VALUE"
}'
Using the Credential Offer
The credentialOffer URL in the response can be:
- Displayed as a QR code – The user scans it with their wallet app.
- Sent as a deep link – The user clicks the link on a mobile device.
- Embedded in an email/message – The user clicks to open in their wallet.
When the wallet resolves this URL, it fetches the full credential offer, displays the credential details to the user, and requests the credential using the appropriate flow.
Next Steps
- Notifications & Session Events – Monitor issuance progress in real time.
- Protocol Flows – Understand the pre-authorized and authorization code sequences.
