---
title: "Issuer Profiles"
description: "Reference for the issuer2-profiles.conf configuration file."
stack: "Community Stack (open source) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-profiles
generated: 2026-07-20
---
# Issuer Profiles Configuration

The `issuer2-profiles.conf` file defines **credential profiles** — templates that bind a credential type (declared in [`credential-issuer-metadata.conf`](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md)) to a concrete signing key, an issuer identifier, and the data that goes into the credential. All of the data defined in the profile can also be later overwritten using runtime overwrites when creating a credential offer.

The file has two parts:

1. **Top-level shared material** — `defaultIssuerKey`, `defaultIssuerDid`, `defaultIssuerX5chain`. These are *not* applied automatically; profiles reuse them through HOCON substitution (see [Shared defaults](#shared-defaults-and-hocon-substitution)).
2. **`profiles`** — a map of named profiles.

Each profile is parsed into a strongly-typed object at startup and validated, so an invalid entry stops the service from loading.

## File Location

```
waltid-services/waltid-issuer-api2/config/issuer2-profiles.conf
```

## Configuration Options

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `defaultIssuerKey` | Object | No | Shared issuer signing key, referenced by profiles via `${defaultIssuerKey}`. |
| `defaultIssuerDid` | String | No | Shared issuer DID, referenced via `${defaultIssuerDid}`. |
| `defaultIssuerX5chain` | Array&lt;String&gt; | No | Shared X.509 certificate chain (PEM) for mDoc / x5c signing, referenced via `${defaultIssuerX5chain}`. |
| `profiles` | Object (map) | No | Credential profiles, keyed by profile ID. |

## Shared defaults

The top-level defined `defaultIssuerKey`, `defaultIssuerDid` and `defaultIssuerX5chain` keys are global configuration values which can be used across multiple profiles via the `${...}` syntax:

```hocon
defaultIssuerKey = {
  type = "jwk"
  jwk = {
    kty = "EC"
    d   = "..."
    crv = "P-256"
    x   = "..."
    y   = "..."
  }
}

defaultIssuerDid = "did:jwk:eyJrdHk..."

profiles = {
  identityCredential = {
    name = "IdentityCredential"
    credentialConfigurationId = "IdentityCredential_jwt_vc_json"
    issuerKey = ${defaultIssuerKey}   # <- substitution
    issuerDid = ${defaultIssuerDid}   # <- substitution
    credentialData = { ... }
  }
}
```

**Info:**

This pattern is just a convenience for sharing one key across many profiles. A profile may equally declare its own inline `issuerKey` / `issuerDid` instead of referencing the defaults.

## Profile properties

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `name` | String | **Yes** | Human-readable profile name (non-blank). |
| `credentialConfigurationId` | String | **Yes** | Must match a key in `credentialConfigurations` in `credential-issuer-metadata.conf`. |
| `issuerKey` | Object | **Yes** | Serialized signing key. Must contain a `type` field. |
| `credentialData` | Object | **Yes** | The credential body template (claims). |
| `issuerDid` | String | No | Issuer DID. Used by `jwt_vc_json` and (optionally) `dc+sd-jwt`. |
| `mapping` | Object | No | Dynamic [data functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) for runtime fields. |
| `selectiveDisclosure` | Object | No | SD-JWT selective-disclosure configuration. |
| `idTokenClaimsMapping` | Object | No | Maps authorization-code-flow ID-token claims into the credential. |
| `mDocNameSpacesDataMappingConfig` | Object | No | Per-namespace JSON→CBOR type conversions for `mso_mdoc`. |
| `x5Chain` | Array&lt;String&gt; | No | X.509 certificate chain (PEM) for x5c-based signing. |
| `notifications` | Object | No | Issuance webhook configuration. |
| `credentialStatus` | Object | No | Credential status (revocation) configuration. |

**Error:**

`name`, `credentialConfigurationId`, `issuerKey` and `credentialData` are all **required** — a profile that omits any of them stops the service from loading. `issuerKey` must contain a `type` field, and `credentialConfigurationId` must reference a credential configuration that actually exists in the metadata file.

### Where each field sits

Every property in the table above is a **direct child of a single profile** — they all sit at the same level, inside one entry of the `profiles` map. The skeleton below shows that layout (the sections further down zoom into each block):

```hocon
profiles = {

  myProfile = {                          # profile ID (left of the "=")

    # ---- required ----
    name = "..."                         # string
    credentialConfigurationId = "..."    # string, must exist in credential-issuer-metadata.conf
    issuerKey = { type = "...", ... }    # object
    credentialData = { ... }             # object (the credential body template)

    # ---- optional ----
    issuerDid = "did:..."                # string
    mapping = { ... }                    # object (data functions)
    selectiveDisclosure = {              # object (SD-JWT only)
      fields = { ... }
      decoyMode = "NONE"
      decoys = 0
    }
    idTokenClaimsMapping = {             # object: "<id-token path>" = "<credentialData path>"
      "$.given_name" = "$.given_name"
    }
    mDocNameSpacesDataMappingConfig = {  # object (mso_mdoc only), keyed by namespace
      "org.iso.18013.5.1" = { entriesConfigMap = { ... } }
    }
    x5Chain = [ "..." ]                  # array of PEM strings
    notifications = {                    # object
      webhook = { url = "..." }
    }
    credentialStatus = { ... }           # object
  }
}
```

### Profile ID and uniqueness rules

Inside the `profiles` block, each entry's **name on the left of the `=` is its profile ID**. In the example below, `identityCredential` is the profile ID:

```hocon
profiles = {
  identityCredential = {   # <- "identityCredential" is the profile ID
    name = "IdentityCredential"
    # ...
  }
}
```

- A profile ID must be non-blank and must **not contain a `.` character**.
- Only **one profile per `credentialConfigurationId`** is allowed — two profiles pointing at the same configuration ID will break issuance for that credential.

### Signing: what's needed for each credential type

Which signing fields a profile needs depends on the credential format:

| Format | Issuer mode |
|--------|-------------|
| `jwt_vc_json` (W3C JWT VC) | `issuerKey` + `issuerDid` (no `x5Chain`). |
| `mso_mdoc` (mDoc) | `issuerKey` + `x5Chain` (no `issuerDid`). |
| `dc+sd-jwt` (SD-JWT VC) | `issuerKey` + **exactly one** of `issuerDid` *or* `x5Chain`. |

## Issuer Key

A **serialized walt.id key**. The `type` field selects the backend and is required. The same key backends apply as elsewhere in walt.id:

| `type` | Backend |
|--------|---------|
| `jwk` | Local in-memory / file JWK. |
| `tse` | HashiCorp Vault Transit Engine. |
| `aws-rest-api` | AWS KMS. |
| `azure-rest-api` | Azure Key Vault. |
| `oci-rest-api` | Oracle Cloud KMS. |

**Error:**

The KMS backend identifiers are `aws-rest-api`, `azure-rest-api` and `oci-rest-api` — **not** `aws`, `azure` or `oci`. Using a short form is an unregistered type and fails at startup.

**Option: Local JWK**

The `jwk` field may be written as a nested HOCON object (the form used by the shipped config):

```hocon
issuerKey = {
  type = "jwk"
  jwk = {
    kty = "EC"
    d   = "..."
    crv = "P-256"
    x   = "..."
    y   = "..."
  }
}
```

**Option: HashiCorp Vault**

```hocon
issuerKey = {
  type = "tse"
  server = "http://vault:8200/v1/transit"
  id = "my-issuer-key"
  auth = {
    accessKey = "hvs.your-vault-token"
  }
  # Optional, for Vault Enterprise namespaces:
  # namespace = "my-namespace"
}
```

**Option: AWS KMS**

```hocon
issuerKey = {
  type = "aws-rest-api"
  id = "arn:aws:kms:eu-central-1:123456789012:key/abcd-..."
  config = {
    auth = {
      accessKeyId = "..."
      secretAccessKey = "..."
      region = "eu-central-1"
    }
  }
}
```

**Option: Azure Key Vault**

```hocon
issuerKey = {
  type = "azure-rest-api"
  id = "https://my-vault.vault.azure.net/keys/my-key/<version>"
  auth = {
    clientId = "..."
    clientSecret = "..."
    tenantId = "..."
    keyVaultUrl = "https://my-vault.vault.azure.net/"
  }
}
```

## Credential Data

`credentialData` is the static data template for the credential body. Fields that should be computed per issuance are filled in by [data functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) defined in the `mapping` object such as `<uuid>`, `<timestamp>`, `<timestamp-in:365d>`, `<issuerDid>` and `<subjectDid>`:

```hocon
credentialData = {
  "@context" = ["https://www.w3.org/2018/credentials/v1"]
  type = ["VerifiableCredential", "IdentityCredential"]
  credentialSubject = {
    id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION"
    given_name = "John"
    family_name = "Doe"
  }
  issuer = { id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" }
}
mapping = {
  id = "<uuid>"
  issuer = { id = "<issuerDid>" }
  credentialSubject = { id = "<subjectDid>" }
  issuanceDate = "<timestamp>"
  expirationDate = "<timestamp-in:365d>"
}
```

For SD-JWT VC, time claims are typically driven with the seconds variants:

```hocon
mapping = {
  id  = "<uuid>"
  iat = "<timestamp-seconds>"
  nbf = "<timestamp-seconds>"
  exp = "<timestamp-in-seconds:365d>"
}
```

## Selective Disclosure

For `dc+sd-jwt` credentials, declare which claims are selectively disclosable. Each field carries `sd` (boolean), and nested objects can be described with `children`. The block may also configure decoy digests:

```hocon
selectiveDisclosure = {
  fields = {
    birth_date  = { sd = true }
    family_name = { sd = false }
    address = {
      sd = true
      children = {
        fields = {
          street = { sd = true }
          city   = { sd = true }
        }
      }
    }
  }
  decoyMode = "NONE"   # NONE | FIXED | RANDOM
  decoys = 0
}
```

## ID Token Claim Mapping

For the authorization-code flow, copy claims from the IdP's ID token into the credential. Each entry is **`"<source>" = "<destination>"`**:

- The **key** is a JSONPath into the **ID token** (the source of the value).
- The **value** is a JSONPath into `credentialData` (where the value is written).

```hocon
idTokenClaimsMapping = {
  # read $.family_name from the ID token, write it to $.registered_family_name in credentialData
  "$.family_name" = "$.registered_family_name"
  "$.given_name"  = "$.registered_given_name"
}
```

**Error:**

**Both sides must be full JSONPath expressions** (starting with `$`), and **both must already resolve to a non-null value** — the source path must exist in the ID token, and the destination path must already exist in your `credentialData` template. The mapping *updates* an existing field; it does not create new ones. Bare keys like `given_name` or `address.street` are **not** valid here.

## mDocNameSpacesDataMappingConfig

### Why this is needed

mDoc credentials (`mso_mdoc`) are **CBOR-encoded** as defined by [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html), and the standard mandates a specific CBOR type for certain data elements:

- **Dates** such as `birth_date`, `issue_date` and `expiry_date` must be a CBOR `full-date`; timestamps must be a `tdate`.
- **Binary** values such as `portrait` must be a CBOR **byte string** (`bstr`).

But you author `credentialData` as JSON, which has no native date or byte-string type — those values arrive as plain text strings. Without instructions, the issuer encodes them as CBOR **text strings**, which is **not standards-conformant**: the resulting credential does not match the CBOR types that ISO/IEC 18013-5 (and profiles built on it, such as the EUDI PID Rulebook) require for those data elements.

`mDocNameSpacesDataMappingConfig` is what tells the issuer which JSON string fields to re-encode into the correct CBOR type.

### Is it required?

The field itself is **optional** — but it is **effectively required for any data element the standard defines as a non-text type** (dates, binary). Plain text and numeric fields (e.g. `family_name`, `document_number`) need no entry; only list the fields that require a conversion.

### Syntax

`credentialData` is organized by namespace, and each namespace maps to an `entriesConfigMap` keyed by claim name:

```hocon
mDocNameSpacesDataMappingConfig = {
  "org.iso.18013.5.1" = {
    entriesConfigMap = {
      birth_date = { type = "string", conversionType = "stringToFullDate" }
      issue_date = { type = "string", conversionType = "stringToFullDate" }
      portrait   = { type = "string", conversionType = "base64StringToByteString" }

      # Arrays and nested objects are described recursively:
      driving_privileges = {
        type = "array"
        arrayConfig = [
          {
            type = "object"
            entriesConfigMap = {
              issue_date  = { type = "string", conversionType = "stringToFullDate" }
              expiry_date = { type = "string", conversionType = "stringToFullDate" }
            }
          }
        ]
      }
    }
  }
}
```

**Supported `conversionType` values:**

| Value | Effect |
|-------|--------|
| `stringToFullDate` | Parses a date string into a CBOR full-date element. |
| `stringToTDate` | Parses a date-time string into a CBOR `tdate` element. |
| `base64StringToByteString` | Decodes standard Base64 into a CBOR byte string. |
| `base64UrlStringToByteString` | Decodes Base64URL into a CBOR byte string. |

## Notifications

Configure an issuance webhook, learn more [here](https://docs.walt.id/community-stack/issuer2/notifications.md):

```hocon
notifications = {
  webhook = {
    url = "https://example.com/webhook/issuance"
  }
}
```

## Credential Status

Embed a status-list reference into every credential issued from this profile (e.g. for revocation/suspension). The accepted shape is **format-specific**:

- **W3C JWT VC** (`jwt_vc_json`) — the object is embedded verbatim as the `credentialStatus` claim:

  ```hocon
  credentialStatus = {
    type = "BitstringStatusListEntry"
    id = "https://issuer.example.com/status/1#94567"
    statusPurpose = "revocation"
    statusListIndex = "94567"
    statusListCredential = "https://issuer.example.com/status/1"
  }
  ```

- **SD-JWT VC** (`dc+sd-jwt`) and **mDoc** (`mso_mdoc`) — a Token Status List reference with `idx` and `uri`:

  ```hocon
  credentialStatus = {
    status_list = {
      idx = 94567
      uri = "https://issuer.example.com/status/1"
    }
  }
  ```

**Info:**

In the Community Stack you host and serve the status list yourself — the issuer only embeds this reference into the credential. The same value can also be supplied as a per-offer runtime override. See [Credential Status](https://docs.walt.id/community-stack/issuer2/credential-offers/credential-status.md) for the full reference across all formats.

## Example Configuration

```hocon
# issuer2-profiles.conf

defaultIssuerKey = {
  type = "jwk"
  jwk = {
    kty = "EC"
    d   = "..."
    crv = "P-256"
    x   = "..."
    y   = "..."
  }
}

defaultIssuerDid = "did:jwk:eyJrdHk..."

defaultIssuerX5chain = [
  """-----BEGIN CERTIFICATE-----
MIIBeTCCAR8CFHrWgrGl5KdefSvRQhR...
-----END CERTIFICATE-----"""
]

profiles = {

  # W3C JWT VC — uses issuerDid
  identityCredential = {
    name = "IdentityCredential"
    credentialConfigurationId = "IdentityCredential_jwt_vc_json"
    issuerKey = ${defaultIssuerKey}
    issuerDid = ${defaultIssuerDid}
    credentialData = {
      "@context" = ["https://www.w3.org/2018/credentials/v1"]
      type = ["VerifiableCredential", "IdentityCredential"]
      credentialSubject = {
        id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION"
        given_name = "John"
        family_name = "Doe"
      }
      issuer = { id = "THIS WILL BE REPLACED WITH DYNAMIC DATA FUNCTION" }
    }
    mapping = {
      id = "<uuid>"
      issuer = { id = "<issuerDid>" }
      credentialSubject = { id = "<subjectDid>" }
      issuanceDate = "<timestamp>"
      expirationDate = "<timestamp-in:365d>"
    }
  }

  # mDoc — uses x5Chain, no issuerDid
  mDL = {
    name = "ISO 18013-5 mDL"
    credentialConfigurationId = "org.iso.18013.5.1.mDL"
    issuerKey = ${defaultIssuerKey}
    credentialData = {
      "org.iso.18013.5.1" = {
        family_name = "Mustermann"
        given_name  = "Erika"
        birth_date  = "1971-09-01"
      }
    }
    mDocNameSpacesDataMappingConfig = {
      "org.iso.18013.5.1" = {
        entriesConfigMap = {
          birth_date = { type = "string", conversionType = "stringToFullDate" }
        }
      }
    }
    x5Chain = ${defaultIssuerX5chain}
  }
}
```

## Related Configuration

- [Credential Issuer Metadata](https://docs.walt.id/community-stack/issuer2/configurations/config-files/credential-issuer-metadata.md) – Declares the credential configurations referenced by `credentialConfigurationId`
- [Issuer Service](https://docs.walt.id/community-stack/issuer2/configurations/config-files/issuer-service.md) – Core service configuration
- [Data Functions](https://docs.walt.id/community-stack/issuer2/data-functions.md) – Dynamic data population in `mapping`
