How to verify a JSON credential presentation using a callback

MATTR VII can use a presentation request flow to verify holder’s data. Holders can view these presentation requests in their digital wallets and choose to respond with a verifiable presentation, created from the JSON credentials they hold.

This guide will walk you through successfully orchestrating this workflow and obtaining the callback response using a localhost. You will then need to take the concepts and apply them to your own implementation. Please contact us if you need any assistance.

You may also use the verify callback express sample app to get a hands-on experience of implementing a Node.js Express server to manage the end-to-end user flow.

Prerequisites

  • Local development environment setup with:
  • The id of a Presentation request template available on your tenant. This can be any of the following:
  • DIDs:
    • Verifier DID: This is a DID associated with the verifier. It must be available on your MATTR VII tenant and have a key type that can be used for messaging (e.g. a ed25519 key type). Refer to Create a did:key for more information.
    • Subject DID: This is a did:key that identifies the intended holder of the credential. This DID is usually retrieved from the intended holder’s digital wallet.
      • Refer to Create a did:key if you need assistance in creating one for testing this feature.
      • In production environments you must have a secure way to obtain the holder’s digital wallet DID:
        • Use DID Auth for any new interactions.
        • Ask the user to share their wallet DID.
        • Request an existing credential as part of a verification workflow, and extract the DID from that interaction.
  • A JSON credential issued to a digital wallet matching the Subject DID.

Overview

Verifying a JSON credential presentation using a callback comprises the following steps:

  1. Setup your local environment: Setup your local environment so that it can accept and view data posted to a configured callback URL.
  2. Create a presentation request: Use an existing presentation template to create a presentation request.
  3. Send a presentation request: Send the created presentation request to an intended holder’s digital wallet.
  4. Receive a verifiable presentation: Upon holder’s consent, the digital wallet responds to the presentation request with a verifiable presentation which is sent to the configured callback URL. MATTR VII then performs the required validation to establish the integrity and authenticity of the data provided.

Setup your local environment

You will need to setup the following on your local environment:

  1. Express: This is a web framework for Node.js.
  2. Ngork: Having a server running locally is fine for calls you generate, but MATTR VII needs a publicly resolvable address to send the credential data to. Ngrok is a port forwarding service that helps us out here.

Credential data is going to be sent to the external service. Please ensure you understand the privacy policy of any 3rd party you decide to use for the purpose of this guide.

Setup Express

  1. Install the Express framework using NPM or Yarn:
SHELL
yarn add express
  1. Add the body-parser module to your environment:
SHELL
yarn add express body-parser
  1. Create a folder and add a file named callback-express.js with the following content:
JAVASCRIPT
'use strict'
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
 
// Use body-parser middleware
 
app.use(bodyParser.json())
 
// Receive a POST request to /callback & print it out to the terminal
 
app.post('/callback', function (req, res) {
    const body = req.body
    console.log(body)
    res.sendStatus(200)
})
 
// listen on port 2000
 
app.listen(2000, function (err) {
    if (err) {
        throw err
    }
    console.log('Server started on port 2000')
})

Alternatively you can clone the MATTR Sample apps repo.

  1. Start the app in a terminal window to create a server listening for calls to /callback on the specified port:
SHELL
node callback-express.js
Callback express started
  1. Open a new terminal window and test your step by making the following POST request and checking that it is being returned:
SHELL
curl --request POST \
  --url http://localhost:2000/callback \
  --header 'Content-Type: application/json' \
  --data '{
  "json": "payload"
}'
Express payload

Setup Ngork

  1. Sign up for Ngrok.
  2. Retrieve a token and apply it to your environment.
  3. Open a new terminal window, and then install and run Ngrok on the port defined in callback-express.js:
SHELL
yarn global add ngrok
 
ngrok http 2000
Callback
  1. Test your public endpoint by making another request, swapping out localhost with your valid Ngrok http domain. We will also update the example payload so that it is easier to identify:
SHELL
curl --request POST \
  --url http://71df02cc96e5.ngrok.io/callback \
  --header 'Content-Type: application/json' \
  --data '{
  "ngrok": "routed"
}'

Your Ngrok terminal window should show the routing:

Routing

While your Express terminal window should show the parsed payload:

Parsed payload

Create a presentation request

Request

Make a request of the following structure to create a presentation request:

HTTP
POST /v2/credentials/web-semantic/presentations/requests
Request body
JSON
{
    "challenge": "GW8FGpP6jhFrl37yQZIM6w",
    "did": "did:web:example.com",
    "templateId": "f95e71b0-9bdf-11ea-aec9-3b5c35fc28c8",
    "callbackUrl": "http://71df02cc96e5.ngrok.io/callback/g87Grad",
    "expiresTime": 1638836401000
}
  • challenge : This unique identifier will be used to validate the presentation response sent to the configured callbackUrl. By comparing challenge with the challengeId value in the response, you can ensure it was sent by the intended MATTR VII tenant.
  • did : Use the Verifier DID.
  • templateId : Use the id value of an existing presentation template that will be used to create the presentation request.
  • callbackUrl : This is the path that you want MATTR VII to message (in the form of a JSON body) once it has received and processed the verifiable presentation from the digital wallet holder:
    • The callback URL will not be present directly in the signed presentation request. We highly recommend protecting the callback endpoint by including a unique identifier in the URL to create a unique callback URL for each request, which would make it hard to guess.
    • Must be a valid URL.
    • Must use the HTTPS protocol.
    • Must not be an IP address.
    • Must be available, accept POST requests and respond with a 200 OK.
    • We recommend the callback endpoint always responds with a 404 header to any unsuccessful calls.
  • expiresTime : Once the time (Unix time) is reached any presentation corresponding to the challenge will not be accepted. For the purpose of testing you may wish to extend this out. Defaults to five minutes if no value is provided.

Response

JSON
{
    "id": "38d9ca70-8b61-48c3-9efc-b67d97410743",
    "callbackUrl": "http://71df02cc96e5.ngrok.io/callback/g87Grad",
    "request": {
        "id": "38d9ca70-8b61-48c3-9efc-b67d97410743",
        "type": "https://mattr.global/schemas/verifiable-presentation/request/QueryByExample",
        "from": "did:web:example.com",
        "created_time": 1607300517863,
        "expires_time": 1638836401000,
        "reply_url": "/v2/credentials/web-semantic/presentations/response",
        "reply_to": ["did:key:z6MkjBWPPa1njEKygyr3LR3pRKkqv714vyTkfnUdP6ToFSH5"],
        "body": {
            "id": "5770506c-9ce9-4d54-b295-68ad2bb12a4c",
            "name": "certificate-presentation",
            "domain": "tenant.vii.mattr.global",
            "query": [
                {
                    "type": "QueryByExample",
                    "credentialQuery": [
                        {
                            "reason": "Please provide your certificate.",
                            "example": {
                                "type": "CourseCredential",
                                "@context": [
                                    "https://www.w3.org/2018/credentials/v1",
                                    "https://mattr.global/contexts/vc-extensions/v2",
                                    "https://schema.org"
                                ],
                                "trustedIssuer": [
                                    {
                                        "issuer": "did:web:organization.com",
                                        "required": true
                                    }
                                ]
                            },
                            "required": true
                        }
                    ]
                }
            ],
            "challenge": "GW8FGpP6jhFrl37yQZIM6w"
        }
    },
    "didcommUri": "didcomm:///v2/credentials/web-semantic/presentations/requests/38d9ca70-8b61-48c3-9efc-b67d97410743/didcommuri"
}
  • id : Used by your tenant to uniquely identify this presentation request.
  • callbackUrl : As provided in the request.
  • request :
    • id : Used by your tenant to uniquely identify this presentation request.
    • type : Indicates request type (queryByFrame or queryByExample).
    • from : Verifier’s DID.
    • created_time : Request creation time (Unix time).
    • expires_time : Request expiry time (Unix time) as provided in the request.
    • reply_url : The URL to forward the response to. This is a MATTR VII endpoint which would relay the response to the defined callbackUrl.
    • reply_to : This is the DID the response message will be sent to.
    • body : The referenced presentation template.
  • didcommUri : This is the presentation request URI. It will be used as communication protocol for the exchange between your MATTR VII tenant and the digital wallet.

Send a presentation request

Once you created the presentation request you need to share it with a holder so that they can accept it and respond with a verifiable presentation. You can share the presentation request in one of the following methods:

  • Send via a DID message
  • Send via a QR code.
  • Send via a Deep link.

Send a presentation request as a DID message

To send the presentation request as a DID message you must first encrypt it and then send it as an encrypted message.

Step 1: Encrypt a presentation request

Make a request of the following structure to encrypt the presentation request:

Request
HTTP
POST /v1/messaging/encrypt

Request body

JSON
{
    "senderDidUrl": "did:web:organization.com#CU6dJt9p8t",
    "recipientDidUrls": ["did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d"],
    "payload": {
        "id": "38d9ca70-8b61-48c3-9efc-b67d97410743",
        "type": "https://mattr.global/schemas/verifiable-presentation/request/QueryByExample",
        "from": "did:web:example.com",
        "created_time": 1607300517863,
        "body": {
            "uri": "didcomm:///v2/credentials/web-semantic/presentations/requests/38d9ca70-8b61-48c3-9efc-b67d97410743/didcommuri"
        }
    }
}
Response
JSON
{
    "jwe": {
        "protected": "eyJhbGciOiJYQzIwUCJ9",
        "recipients": [
            {
                "header": {
                    "alg": "ECDH-1PU+A256KW",
                    "kid": "did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d#z6LSsvqSJkBvVEsDC8cxMHuQ3sKoLRMXB1MdtoLrMUq6A8Rg",
                    "epk": {
                        "kty": "OKP",
                        "crv": "X25519",
                        "x": "JOLnYaD7L-Rszz7fczPhn6MkNre25PUsztzB1RHoz14"
                    },
                    "skid": "did:key:z6MkreuqFq6WrwozTeGKuUDz8bniTFRNAg8f3ZB862YdLp7v#z6LScyz3YLToyoKwZE6Tfq65hgZUkZdHrC4ZqohcUH9X6Twx"
                },
                "encryption_key": "ag5iKzjJOth9Wa68dCVKJW_vnO_Ga0zSJgQp5rIUg69HCzIjuNYhDg"
            }
        ],
        "ciphertext": "xpW-D6sDPpWc_jk87nEyxPX7JQV8_OZpaQft7ySQ5XmNhoj-lQyDkXDncOCyhB7yMSdZrRBNQjKxlEbpY_WLk1hBoWfsTeszVSAuFbX_VKUSJ7GR6rcnWGVNgDfKS8GsyC_owtswXatkF_65_mzFOygctkUmd2eI5bcpQpWjhw2vqnvnWkb7l2J27aWFF_c9cu52dB559j8lwLYyYC9oSMgV5piB6ppfrWBGo_DigjxvJcAYcjFYqFcT6A1nphPhwVTQ2HNfJodbQoseHub8UQdG4qAOcggq5DI84tbqor1SU9rdPH03jPkLgoO_aeXyJg5meITXoFSiu_tRfvf8QQ6vKq6pkTTXs8zKXcBCGhGIyKBNBG4R4RIY1UffTMnJQQQGBble3P06pGOnsnSop0BtygelB9M0ZEwnAUSAQqN1RR4AQwWcn9nH6hHEu1pMhSvhCuFNAPWS-hg24JGGw8Xe3EEZlLH0PM8qpUAfksPq",
        "iv": "FJq5zKvuPiUQIdRcMtiChHCJByuY8XK9",
        "tag": "u8kT0VAAtTswjGXxNpuX0g=="
    }
}
Step 2: Send an encrypted presentation request
Request

Make a request of the following structure to send the encrypted presentation request to the holder’s digital wallet:

HTTP
POST /v1/messaging/send

Request body

JSON
{
    "to": "did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d",
    "message": {
        "protected": "eyJhbGciOiJYQzIwUCJ9",
        "recipients": [
            {
                "header": {
                    "alg": "ECDH-1PU+A256KW",
                    "kid": "did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d#z6LSsvqSJkBvVEsDC8cxMHuQ3sKoLRMXB1MdtoLrMUq6A8Rg",
                    "epk": {
                        "kty": "OKP",
                        "crv": "X25519",
                        "x": "JOLnYaD7L-Rszz7fczPhn6MkNre25PUsztzB1RHoz14"
                    },
                    "skid": "did:key:z6MkreuqFq6WrwozTeGKuUDz8bniTFRNAg8f3ZB862YdLp7v#z6LScyz3YLToyoKwZE6Tfq65hgZUkZdHrC4ZqohcUH9X6Twx"
                },
                "encryption_key": "ag5iKzjJOth9Wa68dCVKJW_vnO_Ga0zSJgQp5rIUg69HCzIjuNYhDg"
            }
        ],
        "ciphertext": "xpW-D6sDPpWc_jk87nEyxPX7JQV8_OZpaQft7ySQ5XmNhoj-lQyDkXDncOCyhB7yMSdZrRBNQjKxlEbpY_WLk1hBoWfsTeszVSAuFbX_VKUSJ7GR6rcnWGVNgDfKS8GsyC_owtswXatkF_65_mzFOygctkUmd2eI5bcpQpWjhw2vqnvnWkb7l2J27aWFF_c9cu52dB559j8lwLYyYC9oSMgV5piB6ppfrWBGo_DigjxvJcAYcjFYqFcT6A1nphPhwVTQ2HNfJodbQoseHub8UQdG4qAOcggq5DI84tbqor1SU9rdPH03jPkLgoO_aeXyJg5meITXoFSiu_tRfvf8QQ6vKq6pkTTXs8zKXcBCGhGIyKBNBG4R4RIY1UffTMnJQQQGBble3P06pGOnsnSop0BtygelB9M0ZEwnAUSAQqN1RR4AQwWcn9nH6hHEu1pMhSvhCuFNAPWS-hg24JGGw8Xe3EEZlLH0PM8qpUAfksPq",
        "iv": "FJq5zKvuPiUQIdRcMtiChHCJByuY8XK9",
        "tag": "u8kT0VAAtTswjGXxNpuX0g=="
    }
}
  • to : Use the intended holder’s Subject DID.
  • message : Use the content of the jwe object from the previous step’s response (do not include the jwe property name, just its content).
Response

A 200 response indicates that the message payload was sent to the service endpoint of the dereferenced DID Document (or the default MATTR service endpoint).

Receive a verifiable presentation

After the presentation request is sent to the holder, they will either scan the QR, click the deep link or open the message notification to open their digital wallet, view the verification request screen and accept it in order for the wallet to generate a presentation response with the required data.

MATTR VII receives the presentation response from the wallet, ensures the validity of the verifiable presentation and returns a response to the configured callbackUrl.

The response structure would differ based on the presentation template type :

  • QueryByExample : Basic verification.
  • QueryByFrame : Selective-disclosure verification.
  • DIDAuth : DID authentication.

Basic verifiable presentation response (QueryByExample)

{
  "presentationType": "QueryByExample",
  "challengeId": "GW8FGpP6jhFrl37yQZIM6w",
  "claims": {
    "id": "did:key:z6MkfxQU7dy8eKxyHpG267FV23agZQu9zmokd8BprepfHALi",
    "https://mattr.global/contexts/vc-extensions/defaultVocab#givenName":"Chris",
    "https://mattr.global/contexts/vc-extensions/defaultVocab#familyName":"Shin",
    "https://mattr.global/contexts/vc-extensions/defaultVocab#dateOfBirth":"01.03.1985"
  },
  "verified": true,
  "holder": "did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d",
  "presentation": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1"
    ],
    "type": [
      "VerifiablePresentation"
    ],
    "verifiableCredential": [
      {
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://w3id.org/security/bbs/v1",
          "https://w3id.org/vc-revocation-list-2020/v1",
          "https://mattr.global/contexts/vc-extensions/v2",
          "https://schema.org"
        ],
        "id": "urn:bnid:_:c14n0",
        "type": [
          "Passport",
          "VerifiableCredential"
        ],
        "description": "Passport of the Government of Kakapo",
        "name": "Passport",
        "credentialStatus": {
          "id": "/v1/revocation-lists/957b46e1-98c7-4f47-ba35-2420d777dcdb#0",
          "type": "RevocationList2020Status",
          "revocationListCredential": "/v1/revocation-lists/957b46e1-98c7-4f47-ba35-2420d777dcdb",
          "revocationListIndex": "0"
        },
        "credentialBranding": {
        "backgroundColor": "#f0f9ff",
        "watermarkImageUrl": "https://i.imgur.com/d9HrmRM.png"
        },
        "credentialSubject": {
          "id": "did:key:z6Mkhpgax4YwewHSNVeYR2DhUjWE2jbrD7n567VTfEyqnzvM",
          "familyName": "Shin",
          "givenName": "Chris",
          "dateOfBirth": "01.03.1985"
        },
        "issuanceDate": "2023-05-01T23:38:21.766Z",
        "issuer": {
          "id": "did:web:governmentofkakapo.com"
          "name": "Government of Kakapo"
        },
        "proof": {
          "type": "BbsBlsSignatureProof2020",
          "created": "2023-05-01T23:38:22Z",
          "nonce": "Ps46iK6YtvISRPswuSGKqLL79RmAKOXb4+BoZEBA3xoLKbRJTZO5BEwpITlcCzzu8hw=",
          "proofPurpose": "assertionMethod",
          "proofValue": "ABY//M+suYngqMhAq5M0WNJ8izPF98uEC9Ooli80eD6BIGL/wv6o8ADs8DxtC7SHa3u0O1C2m8LfOYsVnkcE19sXUHv1P/3DW1r9MTCjBRRbPn2IcDKPQF/PiTMgcFApV+olwzuB+xuCuFqHHbMEyNM0r2h55ZYlrndbz/x939fncI9ObGSYK0Zvspg/zEKhZG8YXPkAAAB0l8UU/27di6liE3VB5gvaehL5LneRrTe8xh4Ir9pamhDqwatG2yf/q/RPGL+1qNFSAAAAAik9SSVpvQM+1KVVmdp4zNPsHbn3925k4vLd+YBcKBdoRu+ed2l/JbzqXbrx8hqYHiNEnJh+fYIjgkme0qAYAL6rVXvcnmQnB6H0MdoBnHvOy2sy0pj9+dnYk8s7Ao04vi8g/R6j8MHn5YZxObPPmAgAAAAGEsf+rf8Kw1azQoQ5q19AkSRBiWmvcdQgfyrFRIajx1UplndnAMaOn2KW2Vt6Mo8mdA5NtbR7gZWEUfGaFjKTFgZ4nnhxQmL0ovgo6pxFj8my3lWokGdWrvbuNvzgNtqWcHJ6Pv70Z2kDvoj/Dj3c+yCiGqjZ5Rv1pqIyYXO8Fetd6woO8CtxtvA2y9oVA2quUR68BRYEbtuxV3Q9OicyCjTBMJkwRJAW63RP/FBegbomW/MoX1ZKarQZe0Ueozzj",
          "verificationMethod": "did:key:zUC7GUZCtnybMRbvBR1K2AexwMnBftUUGHyweWBjgvqY5GHMrkSFHmUqGyNLFUSphZcY8pCiFNykBsdnBRnmR2LBeut7Y3g5f3XvYuT7n8GetW3b6fZiHDRfGAkLzXg4GBiqizk#zUC7GUZCtnybMRbvBR1K2AexwMnBftUUGHyweWBjgvqY5GHMrkSFHmUqGyNLFUSphZcY8pCiFNykBsdnBRnmR2LBeut7Y3g5f3XvYuT7n8GetW3b6fZiHDRfGAkLzXg4GBiqizk"
        }
      }
    ],
    "holder": "did:key:z6Mkjajnoc1pMnqqPFsok75PzxCARfeNqtAyunmQ5HDRG619",
    "proof": [
      {
        "type": "Ed25519Signature2018",
        "created": "2023-05-01T23:41:32Z",
        "challenge": "7TiSFJcKHdIkrm7w84904",
        "domain": "YOUR_TENANT_URL",
        "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..VGzuZRy0k9r_U_pgwbEJE0USsBa97-KY0pPOuKw2JCOzUCuASQ5yaP3n-d_xXyUeybJaMAkAo9haqa9VQd0MAw",
        "proofPurpose": "authentication",
        "verificationMethod": "did:key:z6Mkhpgax4YwewHSNVeYR2DhUjWE2jbrD7n567VTfEyqnzvM#z6Mkhpgax4YwewHSNVeYR2DhUjWE2jbrD7n567VTfEyqnzvM"
      }
    ],
    "credentials": [
      {
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://w3id.org/security/bbs/v1",
          "https://w3id.org/vc-revocation-list-2020/v1",
          "https://mattr.global/contexts/vc-extensions/v2",
          "https://schema.org"
        ],
        "type": [
          "CourseCredential",
          "VerifiableCredential"
        ],
        "description": "Passport of the Kingdom of Kakapo",
        "name": "Passport",
        "credentialStatus": {
          "id": "/v1/revocation-lists/957b46e1-98c7-4f47-ba35-2420d777dcdb#0",
          "type": "RevocationList2020Status",
          "revocationListCredential": "/v1/revocation-lists/957b46e1-98c7-4f47-ba35-2420d777dcdb",
          "revocationListIndex": "0"
        },
        "credentialSubject": {
          "familyName": "Shin",
          "givenName": "Chris"
        },
        "issuer": {
          "id": "did:web:governmentofkakapo.com",
          "name": "Government of Kakapo"
        },
        "credentialBranding": {
          "backgroundColor": "#f0f9ff",
          "watermarkImageUrl": "https://i.imgur.com/d9HrmRM.png"
        }
      }
    ]
  }
}
  • challengeId : MATTR strongly encourages verifying received presentation responses. Compare the challenge provided in the presentation request with the challengeId provided in this presentation response to ensure the response is from the intended MATTR VII tenant.
  • verified : Indicates whether the presented credential has been verified as valid (true) or not (false).
  • holder : This is the DID associated with the holder’s digital wallet. It can now be used to issue credentials and/or send secure messages directly to this wallet.

The response payload includes both the presentation and credentials objects, each containing the list of Verifiable Credentials selected by the holder:

  • presentation includes the original presentations retrieved from the wallet, including the proof for each credential.
  • credentials includes data extracted from the presentation request.

You can use either object based on your implementation.

MATTR is not affiliated with any of the service providers mentioned in this guide and cannot vouch for their offerings.