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 adid: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.
- Refer to
- 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
- 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:
- Setup your local environment: Setup your local environment so that it can accept and view data posted to a configured callback URL.
- Create a presentation request: Use an existing presentation template to create a presentation request.
- Send a presentation request: Send the created presentation request to an intended holder’s digital wallet.
- 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:
- Express: This is a web framework for Node.js.
- 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
- Install the Express framework using NPM or Yarn:
yarn add express
- Add the body-parser module to your environment:
yarn add express body-parser
- Create a folder and add a file named callback-express.js with the following content:
'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.
- Start the app in a terminal window to create a server listening for calls to
/callback
on the specified port:
node callback-express.js
- Open a new terminal window and test your step by making the following POST request and checking that it is being returned:
curl --request POST \
--url http://localhost:2000/callback \
--header 'Content-Type: application/json' \
--data '{
"json": "payload"
}'
Setup Ngork
- Sign up for Ngrok.
- Retrieve a token and apply it to your environment.
- Open a new terminal window, and then install and run Ngrok on the port defined in callback-express.js:
yarn global add ngrok
ngrok http 2000
- 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:
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:
While your Express terminal window should show the parsed payload:
Create a presentation request
Request
Make a request of the following structure to create a presentation request:
POST /v2/credentials/web-semantic/presentations/requests
Request body
{
"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 configuredcallbackUrl
. By comparingchallenge
with thechallengeId
value in the response, you can ensure it was sent by the intended MATTR VII tenant.did
: Use the Verifier DID.templateId
: Use theid
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 thechallenge
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
{
"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
orqueryByExample
).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 definedcallbackUrl
.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 QR code
- Use a tool similar to the following to render the
didcommUri
value from presentation request response as a QR code:
MATTR is not affiliated with any of these service providers and cannot vouch for their offerings.
- Share the generated QR code with the intended holder via your preferred communication channel (e.g. e-mail, messaging system, etc.).
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 thechallenge
provided in the presentation request with thechallengeId
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 theproof
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.