.. and press ENTER to ask a question on web5, how to write code and more.

Skip to main content

Issuing a Known Customer Credential

Known Customer Credentials (KCCs) are Verifiable Credentials designed to streamline the Know Your Customer (KYC) process for tbDEX protocol users. KCCs help in gaining access to PFIs providing regulated financial services.

For a comprehensive exploration of the practical applications and compliance considerations of KCCs, refer to our KCC Compliance Guide which provides detailed examples and insights into how KCCs align with certain regulatory requirements, offering valuable context for those overseeing the implementation and management of KCCs.

Important

This guide is intended for educational purposes only and does not constitute legal advice. Compliance programs may have varying requirements. Consult your legal and/or compliance advisors to ensure that the KCC is consistent with your legal and compliance obligations.

In this guide, we'll cover:

info

For a detailed guide on managing Known Customer Credentials as a Wallet application, please refer to the Known Customer Credential Guide for Wallet Apps

Environment Setup​

If you haven't already, please follow the Credential Issuance Server Setup Guide for detailed instructions on the dependencies and packages needed to set up your server.

IDV Process​

IDV is a critical component of typical KYC requirements, where certain types of Personally Identifying Information (PII) are collected and verified from an individual. IDV is a crucial step before a KCC can be issued.

The IDV flow begins when the customer's agent (e.g. Wallet application) sends an HTTP request to the Issuer's IDV service endpoint specified in the Issuer's Decentralized Identifier (DID) Document.

1. Handling Incoming Requests​

Implement an endpoint to handle incoming GET requests from the Wallet. When a request is received, you'll need to construct the SIOPv2 Authorization Request.

Approach 1: Requesting only id_token​

When a Verifiable Presentation is not an accepted form of IDV, you can respond to the customer's Wallet application by requesting only an id_token from them:

JavaScript
Kotlin
Swift
note

id_token is a Self Issued Identity Token, that authenticates the customer to the Issuer by sharing the customer's Decentralized Identifier. This guide will cover an example id_token to learn more refer to OpenID's specs.

Approach 2: Requesting both id_token and vp_token​

When a Verifiable Presentation is an accepted form of IDV, you can respond to the customer's Wallet application by requesting an id_token and a vp_token from them:

JavaScript
Kotlin
Swift

The SOIPv2 Authorization request above that you as the issuer send to your customer contains the following key information about you as well as a Presentation Definition.

  • client_id: Issuer's DID, establishing the issuer/requestor's identity.
  • response_type: Lets the Wallet know the desired format, with id_token being required identity token and vp_token for optional verifiable presentation token. The inclusion of vp_token means a presentation definition is required within the request.
  • response_uri: Indicates the callback url for the Wallet, ensuring direct communication between the Wallet and the Issuer.
  • nonce: A unique identifier tying together the request and its response.
note

vp_token is a Verifiable Presentation that contains certified and authenticated claims about the customer. In this guide, we'll cover an example vp_token. To learn more refer to OpenID's specs.

2. Encode the SIOPv2 Authorization Request and respond:​

JavaScript
Kotlin
Swift

3. Wallet's SIOPv2 Authorization Response​

The Wallet will then respond with an HTTP POST SIOPv2 Authorization Response. The HTTP request body must include an id_token and optionally a vp_token, both of which must be JSON Web Tokens (JWTs). Here is an example HTTP POST request body (which includes the optional vp_token):

// Wallet.js example HTTP POST request body

{
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6ZGh0OmN1c3RvbWVyRGlkIiwic3ViIjoiZGlkOmRodDpjdXN0b21lckRpZCIsImF1ZCI6Imlzc3VlckRpZC51cmkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsImV4cCI6MTYxODg4NDQ3MywiaWF0IjoxNjE4ODgwODczfQGUuY29tL2NyZWRlbnRpYWxzLzE4NzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiSURDYXJkQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQxOToyMzoyNFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJnaXZlbl9uYW1lIjoiRnJlZHJpayIsImZhbWlseV9uYW1.Imlzc3VlckRpZC51cmkiLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsImV4cCI6MTYxODg4NDQ3MywiaWF0IjoxNjE4ODgwODcz",
"vp_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlt7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiXSwiaWQiOiJodHRwczovL2V4YW1wbGUuY29tL2NyZWRlbnRpYWxzLzE4NzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiSURDYXJkQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQxOToyMzoyNFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJnaXZlbl9uYW1lIjoiRnJlZHJpayIsImZhbWlseV9uYW1lIjoiU3RyJm9tYmVyZyIs.ImJpcnRoZGF0ZSI6IjE5NDktMDEtMjIifX1dLCJob2xkZXIiOiJkaWQ6ZXhhbXBsZTpob2xkZXIiLCJwcm9vZiI6eyJ0eXBlIjoiRWQyNTUxOVNpZ25hdHVyZTIwMTgiLCJjcmVhdGVkIjoiMjAyMS0wMy0xOVQxNTozMDoxNVoiLCJjaGFsbGVuZ2UiOiJuLTBTNl9XekEyTWoiLCJkb21haW4iOiJodHRwczovL2NsaWVudC5leGFtcGxlLm9yZy9jYiIsImp3cyI6ImV5SmhiR2NpT2lKSVV6STFOaUo5LkhvbGRlcl9GT1JfU0lHTkFUVVJFIiwicHJvb2ZQdXJwb3NlIjoiYXV0aGVudGljYXRpb24iLCJ2ZXJpZmljYXRpb25NZXRob2QiOiJkaWQ6ZXhhbXBsZTpob2xkZXIja2V5LTEifX0"
}

The decoded JWT Claims for the id_token and vp_token are as follows:

// Wallet.js id_token decoded JWT claims

{
"iss": "did:dht:customerDid",
"sub": "did:dht:customerDid", // Customer's DID string
"aud": "did:dht:issuerDid", // Issuer's DID string
"nonce": "n-0S6_WzA2Mj", // Unique string from the Issuer's Auth request
"exp": 1618884473, // Expiration time
"iat": 1618880873 // issued at time
}
// Wallet.js vp_token decoded JWT claims

{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://identity.foundation/presentation-exchange/submission/v1"
],
"type": ["VerifiablePresentation", "PresentationSubmission"],
"presentation_submission": {
"id": "epzZXstAcVNt5MRrcyG91",
"definition_id": "IDCardCredentials",
"descriptor_map": [
{
"id": "IDCardCredential",
"format": "jwt_vc",
"path": "$.vp.verifiableCredential[0]"
},
{
"id": "nationalIdentifierVerification",
"format": "jwt_vc",
"path": "$.vp.verifiableCredential[1]"
}
]
},
"verifiableCredential": [
"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyI3o2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtlcjQ5Q251Zzdoc3ZIRGd2NDR5dnBkdnRNaDR5TGlEWHhTNjdobnJVaHR0MiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFbXBsb3ltZW50Q3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjcyNDhiOTkyLTkwOTYtNDk2NS1hMGVjLTc3ZDhhODNhMWRmYiIsImlzc3VlciI6ImRpZDprZXk6ejZNa2VyNDlDbnVnN2hzdkhEZ3Y0NHl2cGR2dE1oNHlMaURYeFM2N2huclVodHQyIiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDoxMToyNVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlEMTR4UmY0cTJNWlh1ZWY2X2ZXYnBGbVlTUG94dGFxTkp1SmdEMG96Wl84UTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWVubGFNbVYzTlhKeVVXdFVjbUV3WlZsVk16WlBTblJzTURCbFJWZHhhalZhV0dkNmNEZFpSVTVKUVNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpQZDJZMFQyMUViamxKWm5SNFdYWnBkRTFHWm1jMVVXeDVMVVV6VWs1b1dsUkdPVlpFTWtnNVQzVjNJaXdpZVNJNkltUnZjVmxtV2s1c1NtRlRNVll4U201bU9HdEZObEF6VkRsd2QzaDNla3hFVTJWc1ZqTlRUa2s1U2xFaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xEWm05bVFUQkpVbU5uY2tWdVVHZHdRbU5RV1ZsV2VFWlliR0pTYjJRd2RVNWZRVkJwTkVrNUxVRmZRU0o5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFtd3pWWG80VldGT2REZGxlREJKYjJJMFJFNXNhbFJGVmpaelQwTmtjbFJ3TWxvNE5FTkJPVFJPUWtFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVOWk9WRldZbWRKYkUxemRraEZYMVJtTld4a1MxQjBkR3d3WVV4blNrdHNSbmt6Vms0d2QzQTJhVFpSSW4xOSIsImVtcGxveW1lbnRTdGF0dXMiOiJlbXBsb3llZCJ9fX0.Sazc8Ndhs-NKjxvtVMKeC9dxjEkI26fVsp2kFNWM-SYLtxMzKvl5ffeWd81ysHgPmBBSk2ar4dMqGgUsyM4gAQ",
"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0I3o2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtqcFM0R1VBaGJnQkpoNmsyZ2U2b1k0NFFMckVwNzVyWnRzamFUS29yUkRkdCIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJOYW1lQW5kRG9iQ3JlZGVudGlhbCJdLCJpZCI6InVybjp1dWlkOjliZjM2YzY5LTI0ODAtNDllZC1iMTYyLTRlZDEwOWE3MTc3NyIsImlzc3VlciI6ImRpZDprZXk6ejZNa2pwUzRHVUFoYmdCSmg2azJnZTZvWTQ0UUxyRXA3NXJadHNqYVRLb3JSRGR0IiwiaXNzdWFuY2VEYXRlIjoiMjAyMy0xMi0yMVQyMDowNjowMVoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDppb246RWlDS2o2M0FyZlBGcEpsb2lTd3gxQUhxVWtpWlNoSDZGdnZoSzRaTl9fZDFtQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUprZDI0dGMybG5JaXdpY0hWaWJHbGpTMlY1U25kcklqcDdJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNscFdXbTVJVkVrNWFEWkJUVmxVV0dwT01HcFhTVkYwTTI5ak4xTnJTeTF4Y2kxcVVuSTBUalEzUlNKOUxDSndkWEp3YjNObGN5STZXeUpoZFhSb1pXNTBhV05oZEdsdmJpSmRMQ0owZVhCbElqb2lTbk52YmxkbFlrdGxlVEl3TWpBaWZTeDdJbWxrSWpvaVpIZHVMV1Z1WXlJc0luQjFZbXhwWTB0bGVVcDNheUk2ZXlKamNuWWlPaUp6WldOd01qVTJhekVpTENKcmRIa2lPaUpGUXlJc0luZ2lPaUpaVDFwRE5WSmlUMHQ1T0dadVVUWTJVWEZPUkc5aldFMXZPVXhUZEdNNVYyOWthMHd0ZFZCZlExQnZJaXdpZVNJNklsWnZZM0UxVERodFozQlhXVTFrYjFwS1JrWlJUa1ZDT0hsR0xXTndkRWQzZFdkcFRWVm5hR2t6Y21jaWZTd2ljSFZ5Y0c5elpYTWlPbHNpYTJWNVFXZHlaV1Z0Wlc1MElsMHNJblI1Y0dVaU9pSktjMjl1VjJWaVMyVjVNakF5TUNKOVhTd2ljMlZ5ZG1salpYTWlPbHQ3SW1sa0lqb2laSGR1SWl3aWMyVnlkbWxqWlVWdVpIQnZhVzUwSWpwN0ltVnVZM0o1Y0hScGIyNUxaWGx6SWpwYklpTmtkMjR0Wlc1aklsMHNJbTV2WkdWeklqcGJJbWgwZEhCek9pOHZaSGR1TG5SaVpHUmxkaTV2Y21jdlpIZHVOaUlzSW1oMGRIQnpPaTh2WkhkdUxuUmlaR1JsZGk1dmNtY3ZaSGR1TUNKZExDSnphV2R1YVc1blMyVjVjeUk2V3lJalpIZHVMWE5wWnlKZGZTd2lkSGx3WlNJNklrUmxZMlZ1ZEhKaGJHbDZaV1JYWldKT2IyUmxJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCTXpSMlMzb3llVmswZVV4dGRDMUdabkJuYWpWbGFFRm1ZWFI1YzFOa2MwNVNWbVpMYkhwUWRqTjVkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFF6ZGZjMXBzTW1wMVVXNUdhRVJIV1RSb2NFVTRiMlF4YVU5MWRuZG1PVFJ5TVVkbk9HMWFWbVJCVmxFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVKdU5sTnJiSEpWYzNKdVFuaFJPVXBqVXkxTlNVaGtWelYwTXpRM1MxWjNaMXBwVEZwMFQwcDRRVkYzSW4xOSIsIm5hbWUiOiJhbGljZSBib2IiLCJkYXRlT2ZCaXJ0aCI6IjEwLTAxLTE5OTAifX19.mNCDv_JntH-wZpYONKNL58UbOWaYXCYJO_HPI_WVlSgwzo6dhYmV_9qtpFKd_exFb-aaEYPeSE43twWlrJeSBg"
]
}

4. Handle SIOPv2 Response​

Implement a /siopv2/response endpoint to process and verify the Wallet's response. The HTTP response body must always include a credential_offer. If the Wallet's response does not include a vp_token, as a valid form of IDV, then the HTTP response body must also include a URL directing the Wallet to an Identity Verification form:

JavaScript
Kotlin
Swift
info

The IDV Request Form points to a webpage for the customer to input their Personal Identifiable Information (PII) (e.g.; name, identification number, date of birth, etc).

Credential Offer Details:​

  • credential_issuer: The base URL address of the Issuer's service. It is used by the Wallet to retrieve the Issuer's metadata and the Issuer's Authorization Server Metadata.
  • credential_configuration_ids: Unique strings that identify the credential you are offering. The Wallet can use these to request metadata.
  • grants: Contains grant types you will accept for this credential offer.
  • urn:ietf:params:oauth:grant-type:pre-authorized_code: Grant type required for the OID4VCI Pre-Authorization flow.
  • pre-authorized_code: The code representing the Credential Issuer's authorization for the Wallet to obtain an access token.
info

Dynamic Metadata Retrieval by Wallets

Wallets use the credential_issuer from the Credential Offer to dynamically construct URLs for fetching Issuer and Authorization Server Metadata. These URLs, formed by appending /.well-known/openid-credential-issuer and /.well-known/oauth-authorization-server to the credential_issuer, allow wallets to access up-to-date information on issuer capabilities and OAuth 2.0 endpoints. The next steps will cover how to properly configure these endpoints.

5. Create Issuer's Metadata Endpoint​

Configure the /.well-known/openid-credential-issuer endpoint that will return your Issuer Metadata:

JavaScript
Kotlin
Swift
  • credential_issuer: The base URL address of the Issuer's service. It is used by the Wallet to retrieve the Issuer's metadata and the Issuer's Authorization Server Metadata.
  • credential_endpoint: The endpoint the Wallet will use to submit requests to issue credentials. After obtaining an access token, the Wallet sends a credential request to this endpoint.
  • credential_configurations_supported: Defines the format jwt_vc_json, and the credential, including informative display markup.

6. Create Issuer's Authorization Server Metadata Endpoint​

Configure the /.well-known/oauth-authorization-server endpoint that will return your Issuer Authorization Server Metadata:

JavaScript
Kotlin
Swift
  • issuer: The base URL of the Issuer's service.
  • token_endpoint: The endpoint where the Wallet can exchange the pre_authorization_code receieved in the credential_offer for an access token.

7. Create Access Token Endpoint​

Set up the /token endpoint to process incoming requests from the Wallet containing the pre_authorization_code. This code, originally provided in the credential_offer object, is used to validate the Wallet's request:

JavaScript
Kotlin
Swift
info

authorization_pending error response in the case IDV is incomplete

The Issuer must check if the IDV Process is completed prior to issuing an access token, and in the event IDV is incomplete must return an authorization_pending error response to the Wallet whereafter the Wallet will repeat the call to /token until IDV is completed.

Design Known Customer Credential​

When creating a Verifiable Credential, you can design a model class to represent the specific type of credential you'd like to issue.

Define a KccCredential class with required and optional fields for a Known Customer Credential:

JavaScript
Kotlin
Swift
  • countryOfResidence: Is a ISO-3166 2-letter country code representing the customer’s verified country of residence (ex: US)

  • tier: The tier your customer falls into, assuming you perform tier-based KYC.

  • credentialSchema: Schema used to define the shape of the credential including its required fields.

  • evidence: Is an Array of objects, where each object contains kind (String) and checks (array of Strings). kind states the type of evidence and checks details the specific proofs or checks used during IDV.

info
  • You'll need to assign issuer, subject, type, and expirationDate properties during the creation process. issuanceDate is managed and generated by the Web5 SDK. You'll see how this works in the upcoming section.

Issue Known Customer Credential​

Issuing a Known Customer Credential to a customer's Wallet confirms the successful completion of the Identity Verification Process. To create and securely sign this credential, we will utilize the web5/credentials package.

  1. Once the customer accepts your Credential Offer, the Wallet will then send a request for credentials to the endpoint specified in the credential_endpoint field of the Issuer's metadata. Here is an example of the Wallet's request:
POST /credentials HTTP/1.1
Host: issuer.example.com
Authorization: Bearer eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJblIwY0VRNWF6SnNSSGQxV2tobmQyVllVbDluVUd4U2RHWk9hMEZUZVhZek1HVlhSSGx5VUU5Uk1VVWlmUSMwIn0.eyJleHAiOjE3MTA5NDkzODAsImlhdCI6MTcxMDg2Mjk1NywiaXNzIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpZUNJNkluUjBjRVE1YXpKc1JIZDFXa2huZDJWWVVsOW5VR3hTZEdaT2EwRlRlWFl6TUdWWFJIbHlVRTlSTVVVaWZRIiwic3ViIjoiZGlkOmp3azpleUpyZEhraU9pSlBTMUFpTENKamNuWWlPaUpGWkRJMU5URTVJaXdpZUNJNklqWnRMVTU0T0dad1IxbFRPRmhXTjFneFVWY3hhbEp1V0c0dFRXUnFVakkzWm1KS1lUaEZWa3RuYTJNaWZRIn0.5_CFzPpvvbxubtXyz7jsmwqaA3SPcebk9OvSWGnORMMfIgh6iWYj5DjXgTst3Mz7ZLjK0AaCF628hwis5hL3AA
Content-Type: application/json
{
"proof": {
"proof_type": "jwt",
"jwt": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJalp0TFU1NE9HWndSMWxUT0ZoV04xZ3hVVmN4YWxKdVdHNHRUV1JxVWpJM1ptSktZVGhGVmt0bmEyTWlmUSMwIiwidHlwIjoib3BlbmlkNHZjaS1wcm9vZitqd3QifQ.eyJhdWQiOiJkaWQ6andrOmV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2llQ0k2SW5SMGNFUTVhekpzUkhkMVdraG5kMlZZVWw5blVHeFNkR1pPYTBGVGVYWXpNR1ZYUkhseVVFOVJNVVVpZlEiLCJpYXQiOjE3MTA4NjMxOTEsIm5vbmNlIjoidFppZ25zbkZicCJ9.MqxVdgX2tJMDJhNYuniYE7bjLyybRgRcPlDCY2ZN9FU2acLC5kU_wAZq-IF8vfIRXfmhbiFoBbfoPUZNBgdDAw"
}
}

The proof JWT contains various claims, most notably a nonce to prevent replay attacks. For a detailed look at each field please refer to the OpenID spec.

  • Authorization: Bearer {jwt}: The access_token that the Wallet recieved from the /token endpoint.
Important

Before issuing a Known Customer Credential, it's important to complete all necessary steps of your Know Your Customer (KYC) program. This may include further screenings on the user's DID/information, such as Sanctions Checks, Politically Exposed Persons (PEP) screenings, and Negative News Screenings, which aren't covered in this guide. Consult with your legal and compliance advisors to ensure that this process is consistent with your specific legal and compliance obligations.

  1. Implement the /credentials endpoint to handle incoming credential requests from the Wallet. This endpoint performs several important steps:
  • Extracts and validates the access_token from the Auth header
  • Validates the Signed Challenge Nonce (c_nonce)
  • Creates the Known Customer Credential
  • Responds with the Signed credential
JavaScript
Kotlin
Swift
info
  • The id referenced in the credentialSchema field must correspond to a valid JSON schema that is hosted on an accessible server.
  • Signing will return a VC JSON Web Token, which is ideal for secure transmission of the credential.

To convert the signed VC JWT into a VerifiableCredential object you can use the parseJwt() method. Here is what your decoded Known Customer Credential would look like:

{
"vcDataModel": {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiableCredential", "KnownCustomerCredential"],
"id": "urn:uuid:12345678-1234-5678-1234-567812345678",
"issuer": "did:dht:z6Mkn4w6nSaWe4fjNJRvaHZwFnMm5VexvjzDeozEu2G7jC34",
"issuanceDate": "2024-05-29T19:23:24Z",
"expirationDate": "2026-05-19T08:02:04Z",
"credentialSubject": {
"id": "did:dht:z6MkjGSeekPGE9QfczHWyW8v2ZzJU68kqSHzV7L2dmQyuyDu",
"countryOfResidence": "US",
"tier": "Gold"
},
"credentialSchema": {
id: "https://schema.org/PFI",
type: "JsonSchema"
},
"evidence": [
{
"kind": "document_verification",
"checks": ["passport", "utility_bill"]
},
{
"kind": "sanction_screening",
"checks": ["PEP"]
}
]
}
}

With that, you've successfully gone through the Identity Verification flow, and issued a Known Customer Credential. Please note that this example is a foundational implementation. For a production environment, it's crucial to enhance this basic setup with comprehensive error handling, and security measures.

Connect with us on Discord

Submit feedback: Open a GitHub issue

Edit this page: GitHub Repo

Contribute: Contributing Guide