Verify credentials
Just like it says on the tin, verifiable credentials are verifiable by design. They contain all the proof needed to enable the owner to answer verification requests directly, without third parties.
Once issued the holder of a credential can accept incoming proof requests to present information to a verifying party. In the same way that you would show a loyalty card to prove you are part of a loyalty program in the physical world, verifiable credentials can be used to prove information in a digital interaction.
This guide contains everything you need to know about verifying credentials with Paradym. It will show you how to to:
- Set up a trusted entity.
- Create a presentation template
- Set up a webhook subscription.
- Use a presentation template to verify a credential
- View your ongoing verifications and their status
Let’s get started! 🚀
Set up a trusted entity
Create a trusted entity following the trusted entities docs.
Create a presentation template
Create a presentation template following the presentation template docs.
Set up a webhook subscription
To listen for the result of a verification, you can set up a webhook subscription. Whenever an event happens within Paradym, we will notify your application by sending the event to the URL of the webhook subscription.
We recommend listening one of the the following event sets, depending on the credential format you’re using:
didcomm.verification.dataanddidcomm.verification.failed(When verifying Anoncreds credentials)openid4vc.verification.dataandopenid4vc.verification.failed(When verifying SD-JWT VC and mDoc credentials)
You can read more on configuring webhook subscriptions in the webhook documentation. This covers how to create a webhook subscription, as well as verifying a webhook (which is important to ensure the webhook was sent by Paradym).
You can use a site such as Webhook.site or Request Baskets to get a temporary endpoint to receive events and play around with.
Note that webhook events contain sensitive information, and these services do not verify the HMAC of the events. You should not use these services for production webhooks.
Use a presentation template to verify a credential
The Paradym API currently supports issuing SD-JWT credentials and verifying SD-JWT and mDoc credentials over OpenID4VC, as well as issuance and verification of Anoncreds credentials over DIDComm. Read more about the different standards and protocols.
Based on the credential format you selected in the presentation template, you should either request a presentation over DIDComm or OpenID4VC:
- SD-JWT VC (
sd-jwt-vc) and mDoc (mdoc) with OpenID4VC Verification . - Anoncreds (
anoncreds) with DIDComm Verification .
When using Paradym as your verifier, revocation status is checked automatically for SD-JWT VC, mDoc, and AnonCreds credentials. Learn more about revocation.
OpenID4VC API
To verify an sd-jwt-vc and/or mDoc credential using OpenID4VCI based on the created template, we will make a POST request to https://api.paradym.id/v1/wallets/{walletId}/openid4vc/verification/request. For detailed information on the endpoint, refer to create OpenID4VC verification request in the API reference.
In the payload below we create a verification request, based on the presentation template we created earlier. Make sure to update the presentationTemplateId to the ID of the presentation template.
Response encryption is enabled by default. You can change that by providing the requireResponseEncryption parameter. We recommend enabling response encryption.
Optionally, you can opt into the experimental redirect flow by providing a redirectUri together with a client-generated challenge. The wallet then sends the user-agent back to this URI after submitting the authorization response, so you can finish the interaction in the browser instead of relying on polling or webhooks. The result is withheld until your client exchanges the challenge for the single-use response_code Paradym puts on the redirect, which binds it to the device that started the flow. See the OpenID4VP redirect flow guide for the full round trip with a worked example.
The redirect-based same-device flow is the more secure option and is what we recommend when assurance matters. A cross-device flow (for example scanning a QR code with a wallet on another device) that does not use this redirect binding is prone to session fixation / cross-device replay, because there is no binding between the device that started the flow and the device that presents the credential. See Assurance level and security model for how to choose the right setup (and note that DC API support for secure cross-device flows is coming soon).
{
"presentationTemplateId": "clu129ps200043eghxvhz22m1",
"requireResponseEncryption": true,
"redirectUri": "https://your-app.example.com/verify/callback",
"challenge": "RANDOM_128_BIT_VALUE"
}The payload that is returned will look as follows:
{
"id": "clu6p64z80001c7xudvp2uvts",
"createdVia": "api",
"authorizationRequestUri": "https://paradym.id/invitation?request_uri=https%3A%2F%2Fparadym.id%2Foid4vp%2Fa744eefb-f7e6-444d-8a38-24c0136e893e%2Fauthorization-requests%2F215d6f1a-14fd-47bc-86c8-2b707124718d",
"authorizationRequestQrUri": "https://paradym.id/invitation/eyJxIjoxfQ?request_uri=https%3A%2F%2Fparadym.id%2Foid4vp%2Fa744eefb-f7e6-444d-8a38-24c0136e893e%2Fauthorization-requests%2F215d6f1a-14fd-47bc-86c8-2b707124718d",
"authorizationRequestDeeplinkUri": "openid4vp://?request_uri=https%3A%2F%2Fparadym.id%2Foid4vp%2Fa744eefb-f7e6-444d-8a38-24c0136e893e%2Fauthorization-requests%2F215d6f1a-14fd-47bc-86c8-2b707124718d&client_id=x509_san_dns%3Averifier.example.com",
"authorizationRequestDeeplinkQrUri": "https://paradym.id/invitation/eyJxIjoxLCJkIjoxfQ?request_uri=https%3A%2F%2Fparadym.id%2Foid4vp%2Fa744eefb-f7e6-444d-8a38-24c0136e893e%2Fauthorization-requests%2F215d6f1a-14fd-47bc-86c8-2b707124718d",
"status": "requested",
"presentationTemplateId": "clu129ps200043eghxvhz22m1",
"credentials": [],
"createdAt": "2023-01-03T00:00:00.000Z",
"updatedAt": "2023-01-03T00:00:00.000Z",
"error": null
}The authorizationRequestUri renders the Paradym invitation page (with a QR code) that deeplinks into a supported wallet, and authorizationRequestQrUri returns that QR code directly as an image so you can embed it on your own website or application. A verification started by Verification with OpenID Connect has createdVia set to oauthAuthorization and no request URIs, since its request belongs to the hosted sign-in page.
To link straight into a wallet without the invitation page use authorizationRequestDeeplinkUri. This is a plain openid4vp:// deeplink you can place behind a button or render yourself. authorizationRequestDeeplinkQrUri returns a QR code image whose contents are that deeplink (instead of the universal link).
Authorizing a transaction
If the presentation template defines transactions, every verification request must provide the values for each of them in transactionData. Entries are matched to the template by type, and attributes must hold exactly the attributes declared by that transaction. Paradym builds the OpenID4VP transaction_data entry from the template, so nothing else is accepted.
{
"presentationTemplateId": "clu129ps200043eghxvhz22m1",
"transactionData": [
{
"type": "example.payment",
"attributes": {
"amount": "45000",
"currency": "ZAR",
"payee": "Woolworths"
}
}
]
}The verification returns the transaction data from creation on. encoded is the exact base64url entry from the authorization request, which is what the wallet hashes and signs, and decoded is its parsed form, with the attributes next to type as OpenID4VP defines it. The credential_ids field Paradym adds to it is the OpenID4VP envelope and can be ignored. Once the status is verified, the holder has signed every entry, and credentialId names the credential of the template that signed it. The signing credential is the one in credentials with that id. encoded and decoded follow the wallet’s verificationDataAccess setting the same way the presented credentials do, while type and credentialId remain.
A response that does not sign every entry fails the verification. The details of the error on the failed verification say whether the holder did not sign a hash of the transaction, signed a hash that does not match the requested transaction, or hashed it with an algorithm the request does not allow.
{
"id": "clu6p64z80001c7xudvp2uvts",
"status": "verified",
"presentationTemplateId": "clu129ps200043eghxvhz22m1",
"transactionData": [
{
"type": "example.payment",
"encoded": "eyJ0eXBlIjoiZXhhbXBsZS5wYXltZW50Iiwi...",
"decoded": {
"type": "example.payment",
"credential_ids": ["payment-account"],
"amount": "45000",
"currency": "ZAR",
"payee": "Woolworths"
},
"credentialId": "payment-account"
}
],
"credentials": [
{
"exchange": "openid4vc",
"format": "sd-jwt-vc",
"isValid": true,
"id": "payment-account",
"type": "https://metadata.paradym.id/types/28dc88-PaymentAccount"
}
]
}View the credential data from the verification
Webhook
If you configured a webhook, your application should have received either an openid4vc.verification.data or an didcomm.verification.data event which includes the credential data received in the verification. In case the verification failed, it’s also possible your application received an openid4vc.verification.failed or a didcomm.verification.failed event.
The payload of the openId4VcVerification attribute in the openid4vc.verification.data event, or the didcommVerification attribute in the didcomm.verification.data event, matches the structure from the API.
Each credential in credentials has the id of the presentation template credential it was presented for, and a type. For mdoc and AnonCreds credentials, type is the doctype or schema id from the template. For an SD-JWT VC it is the vct of the presented credential, which can be a type that extends the type in the template. Both remain after the credential data is removed.
That’s it! 🚀
That concludes this guide. You now know how to:
- Create a presentation template
- Use a presentation template to verify a credential
- View your ongoing verifications and their status
If you have any suggestions, remarks or questions, join the Paradym Community and let us know. Happy building!
Verification data access
The verification data access of a wallet decides how you receive the credential data from a verification, and how long Paradym keeps it, unless the flow releases the data itself (see below). Set it in the dashboard under Settings → Wallet , or with verificationDataAccess when you create or update a wallet through the API.
| Option | How you receive the data | How long Paradym keeps it |
|---|---|---|
webhook (default) | In the openid4vc.verification.data or didcomm.verification.data webhook. Not through the API. | Until the webhook is delivered to your endpoints, at most 15 minutes |
once | Through the “Retrieve verification session by id” endpoint, once. | Until you retrieve it, at most 15 minutes |
15min | Through the API, as often as you need. | 15 minutes |
webhookdelivers the data without an extra request. A webhook that fails is retried within 15 minutes, and is lost after that, so your endpoint has to be reachable. The data is in the webhook body: verify the signature, and make sure your server and any proxy in front of it don’t log request bodies.oncekeeps the credential data out of webhooks. You get theopenid4vc.verification.verifiedordidcomm.verification.verifiedwebhook, and fetch the data with your API key. The first successful request removes the data, so if your application fails after that request, the data can’t be retrieved again.15minworks likeonce, but you can retrieve the data again within 15 minutes, for example when processing failed.
We recommend webhook when your endpoint is reliably reachable. Use once if you prefer to fetch the data yourself or can’t receive webhooks, and 15min if you need to retrieve it more than once. With once, the data is only returned by the “Retrieve verification session by id” endpoints, not by the paginated “Retrieve verification sessions” endpoints.
The 1week and indefinite options are deprecated and can no longer be set. Wallets that still use one of them keep it until you switch, but creating or updating a wallet with 1week or indefinite is rejected.
Two flows release the data themselves, once, and this setting does not apply to them. Neither emits the openid4vc.verification.data webhook:
- The redirect flow (a verification created with a
challengeand aredirectUri) releases it in the response to the completion endpoint. - Verification with OpenID Connect releases it at the code exchange, in the ID token.
