Skip to the content.

CHAPI for VC Issuers

CHAPI integrates easily into issuer websites, allowing your site to issue Verifiable Credentials and present them for storage in the recipient's digital wallet:


Import the CHAPI Polyfill into your Issuer Site

If you're working in vanilla JavaScript, you can add the navigator.credentials and credentialHandlerPolyfill globals to your code and then load the polyfill library:

<script src=""></script>

await credentialHandlerPolyfill.loadOnce();


Or, if you're developing on Node.js, add the credential-handler-polyfill library to your project...

npm i credential-handler-polyfill@3

and then import and load the polyfill library as follows:

import * as CredentialHandlerPolyfill from 'credential-handler-polyfill';

await CredentialHandlerPolyfill.loadOnce();
console.log('Ready to work with credentials!');

Create a WebCredential Object

To communicate a Verifiable Credential over CHAPI, it must be wrapped in a WebCredential object, which is constructed as follows:

1. Make a Verifiable Presentation

Incorporate the Verifiable Credential(s) into a Verifiable Presentation. A Verifiable Presentation contains an array of one or more Verifiable Credentials. For CHAPI, the Verifiable Presentation object does not need to be separately signed.

const testPresentation = {
    "@context": [
    "type": "VerifiablePresentation",
    verifiableCredential: [
        //your Verifiable Credential,
        //other VCs, if you want to include multiple
    //A proof is not required on the Verifiable Presentation (only on the VCs themselves)


2. Make a Web Credential

Add wrapper data to the Verifiable Presentation to construct a WebCredential object. The recommendedHandlerOrigins parameter allows issuers to suggest Credential Handlers (e.g. digital wallets) for the user to receive the data.

const credentialType = 'VerifiablePresentation';
const webCredentialWrapper = new WebCredential(
    credentialType, testPresentation, {
    recommendedHandlerOrigins: [


Issue and Store Credentials

An issuer can get() and store() credentials without knowing anything about the user's wallet. This is intentional; for privacy reasons, the issuer must not be able to query any information (without user consent) about which wallets or credential handlers a user may have installed (otherwise, fingerprinting and other attacks would be possible).

A credential issuer can ask to store a Verifiable Credential during a user gesture event, for example when the user pushes a button to receive a credential.

const result = await;
if(!result) {
  console.log('store credential operation did not succeed');


Handle Empty Results

If the get() or store() operation resolves to a null value, this means one of two things:

As mentioned previously, there is (intentionally) no way for the client to know which of these is the case.

As a developer, the recommended way to handle this situation depends on your specific use case. This dilemma is familiar to mobile app developers asking for specific phone permissions (to access the camera or location, for example). It is up to you to decide whether your app has fallback mechanisms, or whether the operation is required and things come to a halt without it.

Typical ways of handling empty results may include: