Learn how to issue several birth certificates to one wallet using claim sets
Issue a parent their own birth certificate and their children's certificates from a single credential offer, using claim set identifiers and a sample Claims source.
Claim set identifiers are offered as an open beta preview feature and are not generally available yet. They are available on all tenants, and their behavior may change in a way that breaks existing integrations before they become generally available.
Introduction
By default, MATTR VII treats every credential a user holds from the same credential configuration as a copy of one credential. A wallet can store several copies, but MATTR VII cannot give each one different data, or find, update or revoke one of them on its own. Claim sets remove that limit, so one person can hold several credentials of the same type, each with its own data and each managed separately.
In this tutorial you are a birth registry. Alex is a parent who wants three birth certificates in their wallet: their own, and one for each of their children, Sam and Riley. Your registry already has a record number for each certificate:
| Record number | Certificate for |
|---|---|
BC-1001 | Alex |
BC-1002 | Sam |
BC-1003 | Riley |
You will:
- Issue all three certificates from a single credential offer. MATTR VII fetches the data for each one from a sample Claims source, using the record number as the lookup key.
- Find one of the issued certificates again by its record number.
- Issue a fourth certificate for a newborn whose record is not in the Claims source yet, by supplying the data on the offer itself.
Prerequisites
- Access to a MATTR VII tenant.
- An active IACA certificate on your tenant, so it can sign mDocs. If you do not have one yet, follow the Create issuer certificates step of the Pre-authorized Code tutorial.
- A way to expose the sample Claims source to the internet. This tutorial uses a free ngrok account, but a Cloudflare tunnel or your own solution also works.
- A holder app that can claim several credentials of the same type from one offer. You can use the sample holder app from the Holder SDK quickstart guide, built with iOS Holder SDK 6.2.0 or later, Android Holder SDK 7.2.0 or later, or React Native SDK 10.1.0 or later.
Tutorial overview
The following diagram shows what happens when Alex scans the QR code which includes the credential offer:
The wallet never sees your record numbers. It receives one credential identifier per record and uses it to request each certificate. MATTR VII matches each credential identifier back to the right record number before it calls your Claims source.
Tutorial steps
Set up the sample Claims source
-
Clone the MATTR Sample Apps repo to your machine and navigate to the
claims-source-appfolder. -
Copy the
env-templatefile to a new file named.env. -
Change the value of
NGROK_AUTHTOKENto your ngrok authentication token. -
Open the
database.jsonfile. It already contains a record for each family member, identified byrecordId:database.json (excerpt) { "recordId": "BC-1002", "given_name": "Sam", "family_name": "Rivera", "birth_date": "2016-09-03", "birth_place": "Springfield", "parent_1_name": "Alex Rivera", "parent_2_name": "Jordan Rivera" }The records for
BC-1001andBC-1003follow the same shape. -
Start the app either via npm or docker:
Start via npm npm install npm run devor
Start via docker docker compose up --build -
Make note of the
Public Claims Source URLdisplayed in the terminal. You will use it in the next step.
The app responds to GET /claims?recordId=<RECORD_NUMBER> with the claims for that record. If
no record matches, it returns an empty object. You will rely on that in the last step.
Configure the Claims source in your MATTR VII tenant
-
In the navigation panel on the left-hand side, expand the Credential Issuance menu.
-
Select Claims sources.
-
Click the Create new button.
-
In the Name field, enter a meaningful name, for example "Birth registry Claims source".
-
In the URL field, paste the
Public Claims Source URLfrom the previous step. It follows this format:https://<YOUR_NGROK_SUBDOMAIN>/claims. -
In the Authorization type section, select API Key as the type.
-
In the API Key field, paste
supersecretapikey. This is the API key the sample app uses. Use a stronger key in production. -
Paste the following code into the Request parameters field:
Request parameters { "recordId": { "mapFrom": "claimSetId" } }This sends the claim set identifier to the Claims source as a
recordIdquery parameter. For example, for Sam's certificate MATTR VII will callGET /claims?recordId=BC-1002. -
Select Create to create the Claims source.
claimSetId is only sent when the credential request resolves to a claim set. If your own
Claims source endpoint always requires the parameter, add a defaultValue. See
Map from claimSetId.
Create a birth certificate credential configuration
-
In the navigation panel on the left-hand side, expand the Credential Issuance menu.
-
Select mDocs.
-
Select the Create new button.
-
In the Name text box, enter "Birth certificate".
-
In the Description text box, enter "Example birth certificate".
-
In the Credential type text box, enter
org.example.birthcertificate.1. This is an example credential type used for this tutorial. -
Paste the following JSON into the Claim mappings text box:
Claim mappings { "org.example.birthcertificate.1": { "given_name": { "mapFrom": "claims.given_name", "type": "string" }, "family_name": { "mapFrom": "claims.family_name", "type": "string" }, "birth_date": { "mapFrom": "claims.birth_date", "type": "date" }, "birth_place": { "mapFrom": "claims.birth_place", "type": "string" }, "parent_1_name": { "mapFrom": "claims.parent_1_name", "type": "string" }, "parent_2_name": { "mapFrom": "claims.parent_2_name", "type": "string" } } }Each claim is mapped from the data your Claims source returns.
-
Use the Claim source dropdown to select the Claims source you created in the previous step.
-
Enter
12in the Months text box in the Validity for panel. -
Select the Create button to create the credential configuration.
-
Copy the configuration ID shown at the top of the configuration's page. You will use it to create the offer in the next step.
Create one offer for all three certificates
The MATTR Portal does not support claim set identifiers yet, so this step and the steps that follow use the MATTR VII Platform APIs.
Make the following request to create a Pre-authorized Code offer.
Instead of an array of credential configuration IDs, credentials is passed as a map:
POST /v1/openid/offers/pre-authorized{
"credentials": {
"<CREDENTIAL_CONFIGURATION_ID>": ["BC-1001", "BC-1002", "BC-1003"]
},
"expiresIn": {
"minutes": 10
}
}credentials: This is a map whose key is the ID of the birth certificate configuration and its value is the list of record numbers to issue. Each record number is a claim set identifier, and one birth certificate credential is issued for each. Record numbers are keyed by credential configuration ID because a record number only has meaning within one type of credential.<CREDENTIAL_CONFIGURATION_ID>: Replace this with theidfrom the previous step.
Response
{
"id": "e6e5e43c-8053-464a-aca4-ca43da765c97",
"uri": "openid-credential-offer://?credential_offer=...",
"userId": "6e30dd69-c867-4279-afd3-e6619253b4a4",
"expiresAt": "2026-09-24T01:39:00.523Z"
}uri: Convert this value to a QR code so the holder app can scan it. Use one of the following tools, making sure you use thePlain textoption where available:userId: The MATTR VII user that represents Alex. Make a note of it. You will use it in the last step to issue another certificate to the same person.
MATTR is not affiliated with any of these service providers and cannot vouch for their offerings.
Claim the certificates
- Open the sample holder app you built in the Holder SDK quickstart guide.
- Scan the QR code from the previous step.
- Accept the offer.
The app shows three birth certificates, one each for Alex, Sam and Riley. Each has its own data, and each is stored separately.
Behind the scenes, the wallet received three credential identifiers from MATTR VII and made one request with each. For each request, MATTR VII called your Claims source with the matching record number. You can see these calls in the terminal running the sample Claims source.
Find a certificate by its record number
Later, you might need to find the certificate you issued for one record, for example to correct it. Make the following request to find Sam's certificate by its record number:
POST /v1/users/credential-bundles/search{
"claimSetId": "BC-1002"
}Response
{
"data": [
{
"id": "e123f4a5-6b7c-8901-2345-67890abcdef2",
"userId": "6e30dd69-c867-4279-afd3-e6619253b4a4",
"credentialReferenceId": "a248d6c9-3f5e-4b2a-9c1e-2f3b4c5d6e7f",
"credentialConfigurationId": "707e920a-f342-443b-ae24-6946b7b5033e",
"credentialType": "org.example.birthcertificate.1",
"claimSetId": "BC-1002",
"state": "active"
// Remaining response properties
}
]
}Each result is a credential bundle: Sam's certificate as it sits in one wallet. If Alex had claimed it on two devices, you would see two results.
id: ThecredentialBundleId. This identifies Sam's birth certificate in this wallet. It is what you target to update the certificate in place, for example after a name correction.credentialReferenceId: Identifies "Alex's copy of Sam's birth certificate" across every device. See Credential reference.claimSetId: The record number you supplied on the offer.
Add a newborn's certificate with inline values
Alex has a new baby, Jesse. Jesse's record, BC-1004, is not in the sample Claims source yet.
You can still issue the certificate by supplying its data on the offer, in claimSets.
This approach allows you to issue a credential without relying on a Claims source to have the record beforehand.
Make the following request, using the userId you noted earlier so the certificate is issued
to Alex:
POST /v1/openid/offers/pre-authorized{
"userId": "<USER_ID>",
"credentials": {
"<CREDENTIAL_CONFIGURATION_ID>": ["BC-1004"]
},
"claimSets": {
"<CREDENTIAL_CONFIGURATION_ID>": {
"BC-1004": {
"given_name": "Jesse",
"family_name": "Rivera",
"birth_date": "2026-09-01",
"birth_place": "Springfield",
"parent_1_name": "Alex Rivera",
"parent_2_name": "Jordan Rivera"
}
}
},
"expiresIn": {
"minutes": 10
}
}userId: Replace<USER_ID>with theuserIdreturned by the first offer.credentials: Lists the new record number, the same way as before.<CREDENTIAL_CONFIGURATION_ID>: Replace this with theidof the birth certificate credential configuration you created earlier.
claimSets: The data for each record, grouped by credential configuration and then by record number. Every record number inclaimSetsmust also be listed incredentials.<CREDENTIAL_CONFIGURATION_ID>: Replace this with theidof the birth certificate credential configuration you created earlier.
Convert the uri in the response to a QR code and scan it with the sample holder app. Alex now
holds four birth certificates.
MATTR VII still calls your Claims source with recordId=BC-1004, and the sample app returns an
empty object because it has no such record. MATTR VII then uses the values from claimSets. If
your Claims source had returned a value for the same claim, the Claims source value would have
been used instead. See
Decide where the claims come from.
A Claims source must return a 2XX response for issuance to continue. If your own Claims
source returns an error for records it does not hold, issuance fails. Return an empty
object instead, or use a credential configuration without a Claims source when you supply
all the data on the offer.
What's next?
- Update a certificate in place, for example after a
name correction, using the
credentialBundleIdyou found in this tutorial. - Return claim sets from an Interaction hook to let the user sign in and receive the certificates they are entitled to in an Authorization Code flow.
- Learn more about Claims sources.
How would you rate this page?
Last updated on