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:

  1. Issuer2 API running
  2. Credential Profile configured — or use one of the ready-made default profiles

Create an Offer

CURL

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_AUTHORIZED or AUTHORIZED.
  • 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 -1 for 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

FieldDescription
issuerDidOverride the issuer DID
issuerKeyOverride the signing key
x5ChainOverride the X.509 certificate chain
credentialDataOverride the credential data
mappingOverride the data mapping
selectiveDisclosureOverride the SD-JWT selective disclosure configuration
idTokenClaimsMappingOverride ID token claims mapping
mDocNameSpacesDataMappingConfigOverride mDoc data mapping
credentialStatusOverride credential status configuration
notificationsOverride notification settings

Transaction Code Configuration

The txCode object configures PIN requirements:

PropertyTypeDescription
input_modeStringInput type: numeric or text
lengthIntegerLength of the PIN
descriptionStringDescription shown to the user

Using the Credential Offer URL

The credentialOffer URL can be:

  1. Displayed as QR Code – User scans with their wallet app
  2. Sent as Deep Link – User clicks link on mobile device
  3. 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

Last updated on July 1, 2026